diff --git a/CHANGELOG.md b/CHANGELOG.md index 2913eea5080..7d8742ba07b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,51 @@ This is the fourth milestone release of Helidon 2.0. ### Backward incompatible changes +#### gRPC: Renamed several annotations and classes + +As part of gRPC API cleanup, we have renamed the following annotations and classes: + +| Old Name | New Name | +| ------------------------ | ------------------ | +| `@RpcService` | `@Grpc` | +| `@RpcMethod` | `@GrpcMethod` | +| `@GrpcServiceProxy` | `@GrpcProxy` | +| `GrpcClientProxyBuilder` | `GrpcProxyBuilder` | + +While in general we prefer not to break backwards compatibility by renaming public API +classes, we felt that in this case the change was warranted and acceptable, for several reasons: + +1. gRPC API was marked experimental in Helidon 1.x +2. While using gRPC in our own applications, we have realized that the code in some cases + does not read as well as it should, and that some class and annotation names should be changed + to improve both internal API consistency and readability + +We apologize for the inconvenience, but we do feel that the impact of the changes is minimal +and that the changes will be beneficial in the long run. + +#### Internal `helidon-common-metrics` and Related Classes Removed +Later releases of Helidon 1.x included the `helidon-common-metrics` component and related +classes which provided a common interface to ease the transition from MicroProfile +Metrics 1.x to 2.x. +Although intended for use only by Helidon subsystems rather than +by developers and users, the component and its contents had to be public so multiple Helidon +subsystems could use them. +Therefore, user code might have used these elements. + +This release removes this common interface and associated classes. +Any user code that used these internal classes can use the corresponding supported classes in +`io.helidon.metrics:helidon-metrics` and MicroProfile Metrics 2.0 instead. + +|Helidon Artifact |Interfaces/Classes | +|--------------|----------------| +|`io.helidon.common:helidon-common-metrics` |Entire artifact, including all `io.helidon.common.metrics...` classes | +|`io.helidon.metrics:helidon-metrics` |`HelidonMetadata` | +| |`InternalBridgeImpl` | +| |`InternalMetadataBuilderImpl` | +| |`InternalMetadataImpl` | +| |`InternalMetricIDImpl` | + + ## [2.0.0-M3] ### Notes diff --git a/applications/pom.xml b/applications/pom.xml index 6e4cbc27be6..b8b64f9c24c 100644 --- a/applications/pom.xml +++ b/applications/pom.xml @@ -47,7 +47,7 @@ 3.0.0 1.6.0 2.19.1 - 2.0.0-M2 + 2.0.0-RC1 3.0.2 1.5.0.Final 0.5.1 @@ -64,6 +64,11 @@ os-maven-plugin ${version.plugin.os} + + io.helidon.build-tools + helidon-maven-plugin + ${version.plugin.helidon} + diff --git a/archetypes/catalog/catalog.xml.mustache b/archetypes/catalog/catalog.xml.mustache new file mode 100644 index 00000000000..c02d7e59ff1 --- /dev/null +++ b/archetypes/catalog/catalog.xml.mustache @@ -0,0 +1,36 @@ + + + + + + + diff --git a/archetypes/mp/bare/pom.xml b/archetypes/catalog/pom.xml similarity index 82% rename from archetypes/mp/bare/pom.xml rename to archetypes/catalog/pom.xml index c2e22930959..2724b5f6770 100644 --- a/archetypes/mp/bare/pom.xml +++ b/archetypes/catalog/pom.xml @@ -22,9 +22,10 @@ 4.0.0 io.helidon.archetypes - helidon-archetypes-mp + helidon-archetypes-project 2.0.0-SNAPSHOT - helidon-archetypes-mp-bare - Helidon Template Application MP bare - \ No newline at end of file + helidon-archetype-catalog + helidon-archetype-catalog + Helidon Archetype Catalog + diff --git a/archetypes/mp/bare/src/main/resources/META-INF/helidon-archetype-resources.txt b/archetypes/mp/bare/src/main/resources/META-INF/helidon-archetype-resources.txt deleted file mode 100644 index b58740f6166..00000000000 --- a/archetypes/mp/bare/src/main/resources/META-INF/helidon-archetype-resources.txt +++ /dev/null @@ -1,8 +0,0 @@ -pom.xml.mustache -src/main/java/__pkg__/GreetResource.java.mustache -src/main/java/__pkg__/package-info.java.mustache -src/main/resources/META-INF/beans.xml -src/main/resources/META-INF/microprofile-config.properties -src/main/resources/logging.properties -src/main/resources/native-image/reflect-config.json -src/test/java/__pkg__/MainTest.java.mustache \ No newline at end of file diff --git a/archetypes/mp/pom.xml b/archetypes/mp/pom.xml index 9dc3310f845..d1d6da75fe0 100644 --- a/archetypes/mp/pom.xml +++ b/archetypes/mp/pom.xml @@ -25,11 +25,7 @@ helidon-archetypes-project 2.0.0-SNAPSHOT - helidon-archetypes-mp - Helidon Template Application Types for MP - pom - - - bare - + helidon-archetype + helidon-bare-mp + Helidon MP Bare Archetype diff --git a/archetypes/se/bare/src/main/resources/META-INF/helidon-archetype.xml b/archetypes/mp/src/main/resources/META-INF/helidon-archetype.xml.mustache similarity index 82% rename from archetypes/se/bare/src/main/resources/META-INF/helidon-archetype.xml rename to archetypes/mp/src/main/resources/META-INF/helidon-archetype.xml.mustache index 37c507e167a..40dbb436c77 100644 --- a/archetypes/se/bare/src/main/resources/META-INF/helidon-archetype.xml +++ b/archetypes/mp/src/main/resources/META-INF/helidon-archetype.xml.mustache @@ -1,7 +1,7 @@ - + - - - - - - - - + + + + + + + + @@ -94,4 +94,4 @@ - \ No newline at end of file + diff --git a/archetypes/mp/bare/src/main/resources/pom.xml.mustache b/archetypes/mp/src/main/resources/pom.xml.mustache similarity index 100% rename from archetypes/mp/bare/src/main/resources/pom.xml.mustache rename to archetypes/mp/src/main/resources/pom.xml.mustache diff --git a/archetypes/mp/bare/src/main/resources/src/main/java/__pkg__/GreetResource.java.mustache b/archetypes/mp/src/main/resources/src/main/java/__pkg__/GreetResource.java.mustache similarity index 100% rename from archetypes/mp/bare/src/main/resources/src/main/java/__pkg__/GreetResource.java.mustache rename to archetypes/mp/src/main/resources/src/main/java/__pkg__/GreetResource.java.mustache diff --git a/archetypes/mp/bare/src/main/resources/src/main/java/__pkg__/package-info.java.mustache b/archetypes/mp/src/main/resources/src/main/java/__pkg__/package-info.java.mustache similarity index 100% rename from archetypes/mp/bare/src/main/resources/src/main/java/__pkg__/package-info.java.mustache rename to archetypes/mp/src/main/resources/src/main/java/__pkg__/package-info.java.mustache diff --git a/archetypes/mp/bare/src/main/resources/src/main/resources/META-INF/beans.xml b/archetypes/mp/src/main/resources/src/main/resources/META-INF/beans.xml similarity index 100% rename from archetypes/mp/bare/src/main/resources/src/main/resources/META-INF/beans.xml rename to archetypes/mp/src/main/resources/src/main/resources/META-INF/beans.xml diff --git a/archetypes/mp/bare/src/main/resources/src/main/resources/META-INF/microprofile-config.properties b/archetypes/mp/src/main/resources/src/main/resources/META-INF/microprofile-config.properties similarity index 100% rename from archetypes/mp/bare/src/main/resources/src/main/resources/META-INF/microprofile-config.properties rename to archetypes/mp/src/main/resources/src/main/resources/META-INF/microprofile-config.properties diff --git a/archetypes/mp/bare/src/main/resources/src/main/resources/logging.properties b/archetypes/mp/src/main/resources/src/main/resources/logging.properties similarity index 100% rename from archetypes/mp/bare/src/main/resources/src/main/resources/logging.properties rename to archetypes/mp/src/main/resources/src/main/resources/logging.properties diff --git a/archetypes/mp/bare/src/main/resources/src/main/resources/native-image/reflect-config.json b/archetypes/mp/src/main/resources/src/main/resources/native-image/reflect-config.json similarity index 100% rename from archetypes/mp/bare/src/main/resources/src/main/resources/native-image/reflect-config.json rename to archetypes/mp/src/main/resources/src/main/resources/native-image/reflect-config.json diff --git a/archetypes/mp/bare/src/main/resources/src/test/java/__pkg__/MainTest.java.mustache b/archetypes/mp/src/main/resources/src/test/java/__pkg__/MainTest.java.mustache similarity index 100% rename from archetypes/mp/bare/src/main/resources/src/test/java/__pkg__/MainTest.java.mustache rename to archetypes/mp/src/main/resources/src/test/java/__pkg__/MainTest.java.mustache diff --git a/integrations/cdi/jpa-weld/src/test/resources/META-INF/microprofile-config.properties b/archetypes/mp/src/test/resources/projects/it1/archetype.properties similarity index 77% rename from integrations/cdi/jpa-weld/src/test/resources/META-INF/microprofile-config.properties rename to archetypes/mp/src/test/resources/projects/it1/archetype.properties index d24a688101c..e431dcc2d09 100644 --- a/integrations/cdi/jpa-weld/src/test/resources/META-INF/microprofile-config.properties +++ b/archetypes/mp/src/test/resources/projects/it1/archetype.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020 Oracle and/or 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,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -mp.initializer.allow=true -mp.initializer.no-warn=true +package=io.helidon.archetypes.tests +version=0.1-SNAPSHOT +groupId=io.helidon.archetypes.tests +artifactId=mp-it1 \ No newline at end of file diff --git a/archetypes/mp/src/test/resources/projects/it1/goal.txt b/archetypes/mp/src/test/resources/projects/it1/goal.txt new file mode 100644 index 00000000000..597acc76884 --- /dev/null +++ b/archetypes/mp/src/test/resources/projects/it1/goal.txt @@ -0,0 +1 @@ +package \ No newline at end of file diff --git a/archetypes/pom.xml b/archetypes/pom.xml index bcabe7a48a6..76fb5ba4874 100644 --- a/archetypes/pom.xml +++ b/archetypes/pom.xml @@ -27,16 +27,13 @@ io.helidon.archetypes helidon-archetypes-project - Helidon Template Application Types used by CLI + Helidon Archetypes pom - - UTF-8 - - se mp + catalog @@ -50,3 +47,4 @@ + diff --git a/archetypes/se/bare/pom.xml b/archetypes/se/bare/pom.xml deleted file mode 100644 index 90a6203f4b0..00000000000 --- a/archetypes/se/bare/pom.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - 4.0.0 - - io.helidon.archetypes - helidon-archetypes-se - 2.0.0-SNAPSHOT - - helidon-archetypes-se-bare - Helidon Template Application SE bare - \ No newline at end of file diff --git a/archetypes/se/bare/src/main/resources/META-INF/helidon-archetype-resources.txt b/archetypes/se/bare/src/main/resources/META-INF/helidon-archetype-resources.txt deleted file mode 100644 index dacbd1f8938..00000000000 --- a/archetypes/se/bare/src/main/resources/META-INF/helidon-archetype-resources.txt +++ /dev/null @@ -1,8 +0,0 @@ -pom.xml.mustache -src/main/java/__pkg__/Main.java.mustache -src/main/java/__pkg__/GreetService.java.mustache -src/main/java/__pkg__/package-info.java.mustache -src/main/resources/application.yaml -src/main/resources/logging.properties -src/main/resources/native-image/reflect-config.json -src/test/java/__pkg__/MainTest.java.mustache \ No newline at end of file diff --git a/archetypes/se/pom.xml b/archetypes/se/pom.xml index 3bb7a8a1b2e..cbf7f7c7487 100644 --- a/archetypes/se/pom.xml +++ b/archetypes/se/pom.xml @@ -25,11 +25,7 @@ helidon-archetypes-project 2.0.0-SNAPSHOT - helidon-archetypes-se - Helidon Template Application Types for SE - pom - - - bare - + helidon-archetype + helidon-bare-se + Helidon SE Bare Archetype \ No newline at end of file diff --git a/archetypes/mp/bare/src/main/resources/META-INF/helidon-archetype.xml b/archetypes/se/src/main/resources/META-INF/helidon-archetype.xml.mustache similarity index 82% rename from archetypes/mp/bare/src/main/resources/META-INF/helidon-archetype.xml rename to archetypes/se/src/main/resources/META-INF/helidon-archetype.xml.mustache index 37c507e167a..71007761af8 100644 --- a/archetypes/mp/bare/src/main/resources/META-INF/helidon-archetype.xml +++ b/archetypes/se/src/main/resources/META-INF/helidon-archetype.xml.mustache @@ -1,7 +1,7 @@ - + - - - - - - - - + + + + + + + + @@ -94,4 +94,4 @@ - \ No newline at end of file + diff --git a/archetypes/se/bare/src/main/resources/pom.xml.mustache b/archetypes/se/src/main/resources/pom.xml.mustache similarity index 95% rename from archetypes/se/bare/src/main/resources/pom.xml.mustache rename to archetypes/se/src/main/resources/pom.xml.mustache index f33ba516134..729668b91b6 100644 --- a/archetypes/se/bare/src/main/resources/pom.xml.mustache +++ b/archetypes/se/src/main/resources/pom.xml.mustache @@ -23,8 +23,8 @@ helidon-webserver - io.helidon.media.jsonp - helidon-media-jsonp-common + io.helidon.media + helidon-media-jsonp io.helidon.config diff --git a/archetypes/se/bare/src/main/resources/src/main/java/__pkg__/GreetService.java.mustache b/archetypes/se/src/main/resources/src/main/java/__pkg__/GreetService.java.mustache similarity index 100% rename from archetypes/se/bare/src/main/resources/src/main/java/__pkg__/GreetService.java.mustache rename to archetypes/se/src/main/resources/src/main/java/__pkg__/GreetService.java.mustache diff --git a/archetypes/se/bare/src/main/resources/src/main/java/__pkg__/Main.java.mustache b/archetypes/se/src/main/resources/src/main/java/__pkg__/Main.java.mustache similarity index 98% rename from archetypes/se/bare/src/main/resources/src/main/java/__pkg__/Main.java.mustache rename to archetypes/se/src/main/resources/src/main/java/__pkg__/Main.java.mustache index eadb452984f..875350371a2 100644 --- a/archetypes/se/bare/src/main/resources/src/main/java/__pkg__/Main.java.mustache +++ b/archetypes/se/src/main/resources/src/main/java/__pkg__/Main.java.mustache @@ -8,7 +8,7 @@ import java.util.logging.LogManager; import io.helidon.config.Config; import io.helidon.health.HealthSupport; import io.helidon.health.checks.HealthChecks; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.metrics.MetricsSupport; import io.helidon.webserver.Routing; import io.helidon.webserver.ServerConfiguration; diff --git a/archetypes/se/bare/src/main/resources/src/main/java/__pkg__/package-info.java.mustache b/archetypes/se/src/main/resources/src/main/java/__pkg__/package-info.java.mustache similarity index 100% rename from archetypes/se/bare/src/main/resources/src/main/java/__pkg__/package-info.java.mustache rename to archetypes/se/src/main/resources/src/main/java/__pkg__/package-info.java.mustache diff --git a/archetypes/se/bare/src/main/resources/src/main/resources/application.yaml b/archetypes/se/src/main/resources/src/main/resources/application.yaml similarity index 100% rename from archetypes/se/bare/src/main/resources/src/main/resources/application.yaml rename to archetypes/se/src/main/resources/src/main/resources/application.yaml diff --git a/archetypes/se/bare/src/main/resources/src/main/resources/logging.properties b/archetypes/se/src/main/resources/src/main/resources/logging.properties similarity index 100% rename from archetypes/se/bare/src/main/resources/src/main/resources/logging.properties rename to archetypes/se/src/main/resources/src/main/resources/logging.properties diff --git a/archetypes/se/bare/src/main/resources/src/main/resources/native-image/reflect-config.json b/archetypes/se/src/main/resources/src/main/resources/native-image/reflect-config.json similarity index 100% rename from archetypes/se/bare/src/main/resources/src/main/resources/native-image/reflect-config.json rename to archetypes/se/src/main/resources/src/main/resources/native-image/reflect-config.json diff --git a/archetypes/se/bare/src/main/resources/src/test/java/__pkg__/MainTest.java.mustache b/archetypes/se/src/main/resources/src/test/java/__pkg__/MainTest.java.mustache similarity index 97% rename from archetypes/se/bare/src/main/resources/src/test/java/__pkg__/MainTest.java.mustache rename to archetypes/se/src/main/resources/src/test/java/__pkg__/MainTest.java.mustache index a37faa2ac40..1b0082681d5 100644 --- a/archetypes/se/bare/src/main/resources/src/test/java/__pkg__/MainTest.java.mustache +++ b/archetypes/se/src/main/resources/src/test/java/__pkg__/MainTest.java.mustache @@ -8,7 +8,7 @@ import javax.json.Json; import javax.json.JsonBuilderFactory; import javax.json.JsonObject; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; import io.helidon.webserver.WebServer;; diff --git a/archetypes/se/src/test/resources/projects/it1/archetype.properties b/archetypes/se/src/test/resources/projects/it1/archetype.properties new file mode 100644 index 00000000000..907c49265c7 --- /dev/null +++ b/archetypes/se/src/test/resources/projects/it1/archetype.properties @@ -0,0 +1,19 @@ +# +# Copyright (c) 2020 Oracle and/or 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.archetypes.tests +version=0.1-SNAPSHOT +groupId=io.helidon.archetypes.tests +artifactId=se-it1 \ No newline at end of file diff --git a/archetypes/se/src/test/resources/projects/it1/goal.txt b/archetypes/se/src/test/resources/projects/it1/goal.txt new file mode 100644 index 00000000000..597acc76884 --- /dev/null +++ b/archetypes/se/src/test/resources/projects/it1/goal.txt @@ -0,0 +1 @@ +package \ No newline at end of file diff --git a/bom/pom.xml b/bom/pom.xml index 0727e9de43f..66e94a061bb 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -178,33 +178,18 @@ ${helidon.version} - io.helidon.media.jackson - helidon-media-jackson-common - ${helidon.version} - - - io.helidon.media.jackson - helidon-media-jackson-server - ${helidon.version} - - - io.helidon.media.jsonb - helidon-media-jsonb-common - ${helidon.version} - - - io.helidon.media.jsonb - helidon-media-jsonb-server + io.helidon.media + helidon-media-jackson ${helidon.version} - io.helidon.media.jsonp - helidon-media-jsonp-common + io.helidon.media + helidon-media-jsonb ${helidon.version} - io.helidon.media.jsonp - helidon-media-jsonp-server + io.helidon.media + helidon-media-jsonp ${helidon.version} @@ -555,11 +540,6 @@ helidon-common-context ${helidon.version} - - io.helidon.common - helidon-common-metrics - ${helidon.version} - @@ -781,11 +761,6 @@ helidon-integrations-cdi-jpa ${helidon.version} - - io.helidon.integrations.cdi - helidon-integrations-cdi-jpa-weld - ${helidon.version} - io.helidon.integrations.cdi helidon-integrations-cdi-jta @@ -830,8 +805,13 @@ - io.helidon.messaging.connectors.kafka - helidon-messaging-connectors-kafka + io.helidon.messaging + helidon-messaging + ${helidon.version} + + + io.helidon.messaging.kafka + helidon-messaging-kafka ${helidon.version} diff --git a/bundles/webserver/pom.xml b/bundles/webserver/pom.xml index b2c28142dee..0064932f612 100644 --- a/bundles/webserver/pom.xml +++ b/bundles/webserver/pom.xml @@ -44,8 +44,8 @@ helidon-webserver - io.helidon.media.jsonp - helidon-media-jsonp-server + io.helidon.media + helidon-media-jsonp diff --git a/bundles/webserver/src/main/java/module-info.java b/bundles/webserver/src/main/java/module-info.java index 863c81b75bc..1e3b5716021 100644 --- a/bundles/webserver/src/main/java/module-info.java +++ b/bundles/webserver/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020 Oracle and/or 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,5 +19,5 @@ */ module io.helidon.bundles.webserver { requires transitive io.helidon.webserver; - requires transitive io.helidon.media.jsonp.server; + requires transitive io.helidon.media.jsonp; } diff --git a/common/common/src/main/java/io/helidon/common/LazyValue.java b/common/common/src/main/java/io/helidon/common/LazyValue.java index df87395aa4e..fa8014710a6 100644 --- a/common/common/src/main/java/io/helidon/common/LazyValue.java +++ b/common/common/src/main/java/io/helidon/common/LazyValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ static LazyValue create(Supplier supplier) { * @return a lazy value that will always return the value provided */ static LazyValue create(T value) { - return () -> value; + return new LazyValueImpl<>(value); } } diff --git a/common/common/src/main/java/io/helidon/common/LazyValueImpl.java b/common/common/src/main/java/io/helidon/common/LazyValueImpl.java index 9920a0e9f38..4583667fdcb 100644 --- a/common/common/src/main/java/io/helidon/common/LazyValueImpl.java +++ b/common/common/src/main/java/io/helidon/common/LazyValueImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or 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,11 @@ class LazyValueImpl implements LazyValue { private Supplier delegate; private volatile boolean loaded; + LazyValueImpl(T value) { + this.value = value; + this.loaded = true; + } + LazyValueImpl(Supplier supplier) { this.delegate = supplier; } diff --git a/common/configurable/src/main/java/io/helidon/common/configurable/Resource.java b/common/configurable/src/main/java/io/helidon/common/configurable/Resource.java index 00b61f6f736..f513de6c9b5 100644 --- a/common/configurable/src/main/java/io/helidon/common/configurable/Resource.java +++ b/common/configurable/src/main/java/io/helidon/common/configurable/Resource.java @@ -167,7 +167,7 @@ static Resource create(Config resourceConfig) { * @param config configuration * @param prefix prefix of the resource * @return resource if configured - * @deprecated use {@link #create(io.helidon.config.Config)} instead (and change the configuration to use + * @deprecated since 2.0.0 use {@link #create(io.helidon.config.Config)} instead (and change the configuration to use * {@code .resource.type} instead of prefixes */ @Deprecated diff --git a/common/http/src/main/java/io/helidon/common/http/Content.java b/common/http/src/main/java/io/helidon/common/http/Content.java index 78adf090db9..acc70dc6b6f 100644 --- a/common/http/src/main/java/io/helidon/common/http/Content.java +++ b/common/http/src/main/java/io/helidon/common/http/Content.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or 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,11 +16,12 @@ package io.helidon.common.http; -import java.util.concurrent.CompletionStage; import java.util.concurrent.Flow; import java.util.function.Function; import java.util.function.Predicate; +import io.helidon.common.reactive.Single; + /** * Represents an HTTP entity as a {@link Flow.Publisher publisher} of {@link DataChunk chunks} with specific * features. @@ -37,10 +38,10 @@ * {@link #registerFilter(Function)} method. It can be used to wrap or replace previously registered (or default) publisher. * *

Entity Readers

- * It is possible to register function to convert publisher to {@link CompletionStage} of a single entity using + * It is possible to register function to convert publisher to {@link io.helidon.common.reactive.Single} of a single entity using * {@link #registerReader(Class, Reader)} or {@link #registerReader(Predicate, Reader)} methods. It * is then possible to use {@link #as(Class)} method to obtain such entity. - * @deprecated use {@code io.helidon.media.common.MessageBodyReadableContent} instead + * @deprecated since 2.0.0, use {@code io.helidon.media.common.MessageBodyReadableContent} instead */ @Deprecated public interface Content extends Flow.Publisher { @@ -72,7 +73,7 @@ public interface Content extends Flow.Publisher { * * @param function a function that transforms a given publisher (that is either the original * publisher or the publisher transformed by the last previously registered filter). - * @deprecated use {@code io.helidon.media.common.MessageBodyReaderContext.registerFilter} + * @deprecated since 2.0.0, use {@code io.helidon.media.common.MessageBodyReaderContext.registerFilter} */ @Deprecated void registerFilter(Function, Flow.Publisher> function); @@ -92,7 +93,7 @@ public interface Content extends Flow.Publisher { * If an exception is thrown, the resulting completion stage of * {@link #as(Class)} method call ends exceptionally. * @param the requested type - * @deprecated use {@code io.helidon.media.common.MessageBodyReaderContext.registerReader} + * @deprecated since 2.0.0, use {@code io.helidon.media.common.MessageBodyReaderContext.registerReader} */ @Deprecated void registerReader(Class type, Reader reader); @@ -115,7 +116,7 @@ public interface Content extends Flow.Publisher { * If an exception is thrown, the resulting completion stage of * {@link #as(Class)} method call ends exceptionally. * @param the requested type - * @deprecated use {@code io.helidon.media.common.MessageBodyReaderContext.registerReader} + * @deprecated since 2.0.0, use {@code io.helidon.media.common.MessageBodyReaderContext.registerReader} */ @Deprecated void registerReader(Predicate> predicate, Reader reader); @@ -127,9 +128,9 @@ public interface Content extends Flow.Publisher { * (see {@link #registerReader(Predicate, Reader)}). If no such reader is found, the * resulting completion stage ends exceptionally. * - * @param type the requested type class * @param the requested type + * @param type the requested type class * @return a completion stage of the requested type */ - CompletionStage as(Class type); + Single as(Class type); } diff --git a/common/http/src/main/java/io/helidon/common/http/ContextualRegistry.java b/common/http/src/main/java/io/helidon/common/http/ContextualRegistry.java deleted file mode 100644 index 42596c3efd4..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/ContextualRegistry.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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.http; - -import io.helidon.common.context.Context; - -/** - * A registry for context objects. Enables instance localization between several services / components / ... integrated in - * a particular known scope. ContextualRegistry instance is intended to be associated with a scope aware object such as - * WebServer, ServerRequest or ClientRequest. - * - *

Context contains also a notion of classifiers. Classifier is any object defining additional key for registered - * objects. To obtain such registered object, the same classifier (precisely, any equal object) has to be used. - * - *

Classifiers can be used as follows:

    - *
  1. As an additional identifier for registered objects of common types, like a {@link String}, ...
    - *
    {@code
    - * // User detail provider service
    - * registry.register("NAME_PARAM_ID", "Smith");
    - * registry.register("GENDER_PARAM_ID", "male");
    - * ...
    - * // User consumer service
    - * String name = registry.get("name", String.class);
    - * }
  2. - *
  3. As an access control mechanism where only owners of the classifier can retrieve such contextual instance.
    - *
    {@code
    - * // In some central security service.
    - * registry.register(securityFrameworkInternalInstance, new AuthenticatedInternalIdentity(...));
    - * ...
    - * // In some authorization filter known by a central security service
    - * AuthenticatedInternalIdentity auth = registry.get(securityFrameworkInternalInstance, AuthenticatedInternalIdentity.class);
    - * }
  4. - *
- * - * @deprecated This class will be replaced with {@link io.helidon.common.context.Context} in future Helidon versions - */ -@Deprecated -public interface ContextualRegistry extends Context { - - /** - * Creates a new empty instance. - * - * @return new instance - * @deprecated use {@link io.helidon.common.context.Context#create()} - */ - @Deprecated - static ContextualRegistry create() { - return builder().build(); - } - - /** - * Creates a new empty instance backed by its parent read-through {@link ContextualRegistry}. - * - *

Parent {@code registry} is used only for get methods and only if this registry doesn't have registered required type. - * - * @param parent a parent registry - * @return new instance - * @deprecated use {@link io.helidon.common.context.Context#create(io.helidon.common.context.Context)} - */ - @Deprecated - static ContextualRegistry create(Context parent) { - return builder().parent(parent).build(); - } - - /** - * Fluent API builder for advanced configuration. - * - * @return a new builder - * @deprecated used for backward compatibility only - */ - @Deprecated - static Builder builder() { - return new Builder(); - } - - /** - * Fluent API builder for {@link io.helidon.common.http.ContextualRegistry}. - */ - class Builder implements io.helidon.common.Builder { - private Context parent; - private String id; - - @Override - public ContextualRegistry build() { - return new ListContextualRegistry(this); - } - - /** - * Parent of the new context. - * @param parent parent context - * - * @return updated builder instance - */ - public Builder parent(Context parent) { - this.parent = parent; - return this; - } - - /** - * Identification of the new context, should be unique within this runtime. - * - * @param id context identification - * @return updated builder instance - */ - public Builder id(String id) { - this.id = id; - return this; - } - - Context parent() { - return parent; - } - - String id() { - return id; - } - } -} diff --git a/common/http/src/main/java/io/helidon/common/http/DataChunk.java b/common/http/src/main/java/io/helidon/common/http/DataChunk.java index bb8af27958d..6660c9d30d4 100644 --- a/common/http/src/main/java/io/helidon/common/http/DataChunk.java +++ b/common/http/src/main/java/io/helidon/common/http/DataChunk.java @@ -17,6 +17,8 @@ package io.helidon.common.http; import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -25,7 +27,7 @@ *

* The DataChunk and the content it carries stay immutable as long as method * {@link #release()} is not called. After that, the given instance and the associated - * data structure instances (e.g., the {@link ByteBuffer} obtained by {@link #data()}) + * data structure instances (e.g., the {@link ByteBuffer} array obtained by {@link #data()}) * should not be used. The idea behind this class is to be able to * minimize data copying; ideally, in order to achieve the best performance, * to not copy them at all. However, the implementations may choose otherwise. @@ -35,14 +37,15 @@ * threads may result in a race condition unless an external synchronization is used. */ @FunctionalInterface -public interface DataChunk { +public interface DataChunk extends Iterable { + /** * Creates a simple {@link ByteBuffer} backed data chunk. The resulting * instance doesn't have any kind of a lifecycle and as such, it doesn't need * to be released. * * @param byteBuffer a byte buffer to create the request chunk from - * @return a request chunk + * @return a data chunk */ static DataChunk create(ByteBuffer byteBuffer) { return create(false, byteBuffer); @@ -54,106 +57,76 @@ static DataChunk create(ByteBuffer byteBuffer) { * to be released. * * @param bytes a byte array to create the request chunk from - * @return a request chunk + * @return a data chunk */ static DataChunk create(byte[] bytes) { - return create(false, ByteBuffer.wrap(bytes)); + return create(false, false, ByteBuffer.wrap(bytes)); + } + + /** + * Creates a data chunk backed by one or more ByteBuffer. The resulting + * instance doesn't have any kind of a lifecycle and as such, it doesn't need + * to be released. + * + * @param byteBuffers the data for the chunk + * @return a data chunk + */ + static DataChunk create(ByteBuffer... byteBuffers) { + return new DataChunkImpl(false, false, byteBuffers); } /** * Creates a reusable data chunk. * - * @param flush a signal that chunk should be written and flushed from any cache if possible - * @param data a data chunk. Should not be reused until {@code releaseCallback} is used + * @param flush a signal that this chunk should be written and flushed from any cache if possible + * @param byteBuffers the data for this chunk. Should not be reused until {@code releaseCallback} is used * @return a reusable data chunk with no release callback */ - static DataChunk create(boolean flush, ByteBuffer data) { - return create(flush, data, Utils.EMPTY_RUNNABLE, false); + static DataChunk create(boolean flush, ByteBuffer... byteBuffers) { + return new DataChunkImpl(flush, false, byteBuffers); } /** * Creates a reusable data chunk. * - * @param flush a signal that chunk should be written and flushed from any cache if possible - * @param data a data chunk. Should not be reused until {@code releaseCallback} is used - * @param readOnly indicates underlying buffer is not reused + * @param flush a signal that this chunk should be written and flushed from any cache if possible + * @param readOnly indicates underlying buffers are not reused + * @param byteBuffers the data for this chunk. Should not be reused until {@code releaseCallback} is used * @return a reusable data chunk with no release callback */ - static DataChunk create(boolean flush, ByteBuffer data, boolean readOnly) { - return create(flush, data, Utils.EMPTY_RUNNABLE, readOnly); + static DataChunk create(boolean flush, boolean readOnly, ByteBuffer... byteBuffers) { + return new DataChunkImpl(flush, readOnly, byteBuffers); } /** - * Creates a reusable data chunk. + * Creates a reusable byteBuffers chunk. * - * @param flush a signal that chunk should be written and flushed from any cache if possible - * @param data a data chunk. Should not be reused until {@code releaseCallback} is used + * @param flush a signal that this chunk should be written and flushed from any cache if possible * @param releaseCallback a callback which is called when this chunk is completely processed and instance is free for reuse + * @param byteBuffers the data for this chunk. Should not be reused until {@code releaseCallback} is used * @return a reusable data chunk with a release callback */ - static DataChunk create(boolean flush, ByteBuffer data, Runnable releaseCallback) { - return create(flush, data, releaseCallback, false); + static DataChunk create(boolean flush, Runnable releaseCallback, ByteBuffer... byteBuffers) { + return new DataChunkImpl(flush, false, releaseCallback, byteBuffers); } /** - * Creates a reusable data chunk. + * Creates a reusable byteBuffers chunk. * - * @param flush a signal that chunk should be written and flushed from any cache if possible - * @param data a data chunk. Should not be reused until {@code releaseCallback} is used + * @param flush a signal that this chunk should be written and flushed from any cache if possible + * @param readOnly indicates underlying buffers are not reused + * @param byteBuffers the data for this chunk. Should not be reused until {@code releaseCallback} is used * @param releaseCallback a callback which is called when this chunk is completely processed and instance is free for reuse - * @param readOnly indicates underlying buffer is not reused * @return a reusable data chunk with a release callback */ - static DataChunk create(boolean flush, ByteBuffer data, Runnable releaseCallback, boolean readOnly) { - return new DataChunk() { - private boolean isReleased = false; - private CompletableFuture writeFuture; - - @Override - public ByteBuffer data() { - return data; - } - - @Override - public boolean flush() { - return flush; - } - - @Override - public void release() { - releaseCallback.run(); - isReleased = true; - } - - @Override - public boolean isReleased() { - return isReleased; - } - - @Override - public boolean isReadOnly() { - return readOnly; - } - - @Override - public void writeFuture(CompletableFuture writeFuture) { - this.writeFuture = writeFuture; - } - - @Override - public Optional> writeFuture() { - return Optional.ofNullable(writeFuture); - } - }; + static DataChunk create(boolean flush, boolean readOnly, Runnable releaseCallback, ByteBuffer... byteBuffers) { + return new DataChunkImpl(flush, readOnly, releaseCallback, byteBuffers); } /** - * Returns a representation of this chunk as a ByteBuffer. Multiple calls - * of this method always return the same ByteBuffer instance. As such, when - * the buffer is read, the subsequent call of the {@link #data()} returns - * a buffer that is also already read. + * Returns a representation of this chunk as an array of ByteBuffer. *

- * It is expected the returned ByteBuffer holds a reference to data that + * It is expected the returned byte buffers hold references to data that * will become stale upon calling method {@link #release()}. (For instance, * the memory segment is pooled by the underlying TCP server and is reused * for a subsequent request chunk.) The idea behind this class is to be able to @@ -163,10 +136,44 @@ public Optional> writeFuture() { * Note that the methods of this instance are expected to be called by a single * thread; if not, external synchronization must be used. * - * @return a ByteBuffer representation of this chunk that is guarantied to stay + * @return an array of ByteBuffer representing the data of this chunk that are guarantied to stay * immutable as long as method {@link #release()} is not called */ - ByteBuffer data(); + ByteBuffer[] data(); + + /** + * Returns the sum of elements between the current position and the limit of each of the underlying ByteBuffer. + * + * @return The number of elements remaining in all underlying buffers + */ + default int remaining() { + int remaining = 0; + for (ByteBuffer byteBuffer : data()) { + remaining += byteBuffer.remaining(); + } + return remaining; + } + + @Override + default Iterator iterator() { + final ByteBuffer[] byteBuffers = data(); + return new Iterator() { + private int index = 0; + + @Override + public boolean hasNext() { + return index < byteBuffers.length; + } + + @Override + public ByteBuffer next() { + if (index < byteBuffers.length) { + return byteBuffers[index++]; + } + throw new NoSuchElementException(); + } + }; + } /** * The tracing ID of this chunk. @@ -178,10 +185,9 @@ default long id() { } /** - * Gets the content of the underlying {@link ByteBuffer} as an array of bytes. - * If the the ByteBuffer was read, the returned array contains only the part of - * data that wasn't read yet. On the other hand, calling this method doesn't cause - * the underlying {@link ByteBuffer} to be read. + * Gets the content of the underlying byte buffers as an array of bytes. + * The returned array contains only the part of data that wasn't read yet. + * Calling this method doesn't cause the underlying byte buffers to be read. *

* It is expected the returned byte array holds a reference to data that * will become stale upon calling method {@link #release()}. (For instance, @@ -197,12 +203,23 @@ default long id() { * method {@link #release()} is not called */ default byte[] bytes() { - return Utils.toByteArray(data().asReadOnlyBuffer()); + byte[] bytes = null; + for (ByteBuffer byteBuffer : data()) { + if (bytes == null) { + bytes = Utils.toByteArray(byteBuffer.asReadOnlyBuffer()); + } else { + byte[] newBytes = new byte[bytes.length + byteBuffer.remaining()]; + System.arraycopy(bytes, 0, newBytes, 0, bytes.length); + Utils.toByteArray(byteBuffer.asReadOnlyBuffer(), newBytes, bytes.length); + bytes = newBytes; + } + } + return bytes == null ? new byte[0] : bytes; } /** * Whether this chunk is released and the associated data structures returned - * by methods (such as {@link #data()} or {@link #bytes()}) should not be used. + * by methods (such as {@link #iterator()} or {@link #bytes()}) should not be used. * The implementations may choose to not implement this optimization and to never mutate * the underlying memory; in such case this method does no-op. *

@@ -217,7 +234,7 @@ default boolean isReleased() { /** * Releases this chunk. The underlying data as well as the data structure instances returned by - * methods {@link #bytes()} and {@link #data()} may become stale and should not be used + * methods {@link #bytes()} and {@link #iterator()} may become stale and should not be used * anymore. The implementations may choose to not implement this optimization and to never mutate * the underlying memory; in such case this method does no-op. *

@@ -247,11 +264,15 @@ default boolean flush() { * @return A copy of this data chunk. */ default DataChunk duplicate() { - byte[] bytes = new byte[data().limit()]; - data().get(bytes); - DataChunk dup = DataChunk.create(bytes); - dup.data().position(0); - return dup; + ByteBuffer[] byteBuffers = data(); + ByteBuffer[] byteBuffersCopy = new ByteBuffer[byteBuffers.length]; + for (int i = 0; i < byteBuffers.length; i++) { + byte[] bytes = new byte[byteBuffers[i].limit()]; + byteBuffers[i].get(bytes); + byteBuffers[i].position(0); + byteBuffersCopy[i] = ByteBuffer.wrap(bytes); + } + return DataChunk.create(byteBuffersCopy); } /** @@ -271,7 +292,15 @@ default boolean isReadOnly() { * @return Outcome of test. */ default boolean isFlushChunk() { - return flush() && data().limit() == 0; + if (!flush()) { + return false; + } + for (ByteBuffer byteBuffer : data()) { + if (byteBuffer.limit() != 0) { + return false; + } + } + return true; } /** diff --git a/common/http/src/main/java/io/helidon/common/http/DataChunkImpl.java b/common/http/src/main/java/io/helidon/common/http/DataChunkImpl.java new file mode 100644 index 00000000000..2772a27a038 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/DataChunkImpl.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.http; + +import java.nio.ByteBuffer; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * Default implementation of {@link DataChunk}. + */ +final class DataChunkImpl implements DataChunk { + + private final ByteBuffer[] byteBuffers; + private final boolean flush; + private final boolean readOnly; + private final Runnable releaseCallback; + private boolean isReleased = false; + private CompletableFuture writeFuture; + + /** + * Create a new data chunk. + * @param flush a signal that this chunk should be written and flushed from any cache if possible + * @param readOnly indicates underlying buffers are not reused + * @param byteBuffers the data for this chunk. Should not be reused until {@code releaseCallback} is used + */ + DataChunkImpl(boolean flush, boolean readOnly, ByteBuffer... byteBuffers) { + this.flush = flush; + this.readOnly = readOnly; + this.releaseCallback = null; + this.byteBuffers = Objects.requireNonNull(byteBuffers, "byteBuffers is null"); + } + + /** + * Create a new data chunk. + * @param flush a signal that this chunk should be written and flushed from any cache if possible + * @param readOnly indicates underlying buffers are not reused + * @param releaseCallback a callback which is called when this chunk is completely processed and instance is free for reuse + * @param byteBuffers the data for this chunk. Should not be reused until {@code releaseCallback} is used + */ + DataChunkImpl(boolean flush, boolean readOnly, Runnable releaseCallback, ByteBuffer... byteBuffers) { + this.flush = flush; + this.readOnly = readOnly; + this.releaseCallback = Objects.requireNonNull(releaseCallback, "release callback is null"); + this.byteBuffers = Objects.requireNonNull(byteBuffers, "byteBuffers is null"); + } + + @Override + public boolean flush() { + return flush; + } + + @Override + public ByteBuffer[] data() { + return byteBuffers; + } + + @Override + public boolean isReleased() { + return isReleased; + } + + @Override + public boolean isReadOnly() { + return readOnly; + } + + @Override + public void release() { + if (releaseCallback != null) { + releaseCallback.run(); + } + isReleased = true; + } + + @Override + public void writeFuture(CompletableFuture writeFuture) { + this.writeFuture = writeFuture; + } + + @Override + public Optional> writeFuture() { + return Optional.ofNullable(writeFuture); + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/Http.java b/common/http/src/main/java/io/helidon/common/http/Http.java index 4a7835812c1..7e1b2f6d233 100644 --- a/common/http/src/main/java/io/helidon/common/http/Http.java +++ b/common/http/src/main/java/io/helidon/common/http/Http.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -360,8 +360,8 @@ public enum Method implements RequestMethod { /** * The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI. - * If the Request-URI refers to a data-producing tryProcess, it is the produced data which shall be returned as the entity - * in the response and not the source text of the tryProcess, unless that text happens to be the output of the tryProcess. + * If the Request-URI refers to a data-producing process, it is the produced data which shall be returned as the entity + * in the response and not the source text of the process, unless that text happens to be the output of the tryProcess. */ GET, diff --git a/common/http/src/main/java/io/helidon/common/http/ListContextualRegistry.java b/common/http/src/main/java/io/helidon/common/http/ListContextualRegistry.java deleted file mode 100644 index b94277ebf0b..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/ListContextualRegistry.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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.http; - -import java.util.Optional; -import java.util.function.Supplier; - -import io.helidon.common.context.Context; - -/** - * A {@link ContextualRegistry} implementation with deque registry. - */ -class ListContextualRegistry implements ContextualRegistry { - private final Context delegate; - - ListContextualRegistry(ContextualRegistry.Builder builder) { - String configuredId = builder.id(); - Context parent = builder.parent(); - - Context.Builder delegateBuilder = Context.builder(); - - if (null != parent) { - if (parent instanceof ListContextualRegistry) { - delegateBuilder.parent(((ListContextualRegistry) parent).delegate); - } else { - delegateBuilder.parent(parent); - } - } - - if (null != configuredId) { - delegateBuilder.id(configuredId); - } - - this.delegate = delegateBuilder.build(); - } - - @Override - public String id() { - return delegate.id(); - } - - @Override - public void register(T instance) { - delegate.register(instance); - } - - @Override - public void supply(Class type, Supplier supplier) { - delegate.supply(type, supplier); - } - - @Override - public Optional get(Class type) { - return delegate.get(type); - } - - @Override - public void register(Object classifier, T instance) { - delegate.register(classifier, instance); - } - - @Override - public void supply(Object classifier, Class type, Supplier supplier) { - delegate.supply(classifier, type, supplier); - } - - @Override - public Optional get(Object classifier, Class type) { - return delegate.get(classifier, type); - } -} diff --git a/common/http/src/main/java/io/helidon/common/http/Reader.java b/common/http/src/main/java/io/helidon/common/http/Reader.java index 9e01f4d8f75..1932aeb00a4 100644 --- a/common/http/src/main/java/io/helidon/common/http/Reader.java +++ b/common/http/src/main/java/io/helidon/common/http/Reader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ * The Reader transforms a {@link DataChunk} publisher into a completion stage of the associated type. * * @param the requested type - * @deprecated use {@code io.helidon.media.common.MessageBodyReader} instead + * @deprecated since 2.0.0, use {@code io.helidon.media.common.MessageBodyReader} instead */ @FunctionalInterface @Deprecated diff --git a/common/http/src/main/java/io/helidon/common/http/Utils.java b/common/http/src/main/java/io/helidon/common/http/Utils.java index e8a2ae06d99..421c20fcf10 100644 --- a/common/http/src/main/java/io/helidon/common/http/Utils.java +++ b/common/http/src/main/java/io/helidon/common/http/Utils.java @@ -117,13 +117,15 @@ public static void write(ByteBuffer byteBuffer, OutputStream out) throws IOExcep */ public static byte[] toByteArray(ByteBuffer byteBuffer) { byte[] buff = new byte[byteBuffer.remaining()]; + return toByteArray(byteBuffer, buff, 0); + } + static byte[] toByteArray(ByteBuffer byteBuffer, byte[] buff, int destPos) { if (byteBuffer.hasArray()) { - System.arraycopy(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), buff, 0, buff.length); + System.arraycopy(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), buff, destPos, buff.length); } else { - byteBuffer.get(buff); + byteBuffer.get(buff, destPos, byteBuffer.remaining()); } - return buff; } } diff --git a/common/http/src/test/java/io/helidon/common/http/DataChunkTest.java b/common/http/src/test/java/io/helidon/common/http/DataChunkTest.java index eaf98d3142a..3eb25a76fc7 100644 --- a/common/http/src/test/java/io/helidon/common/http/DataChunkTest.java +++ b/common/http/src/test/java/io/helidon/common/http/DataChunkTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ public void testSimpleWrapping() { assertThat(chunk.flush(), is(false)); assertThat(chunk.id(), not(0L)); assertThat(chunk.isReleased(), is(false)); - assertThat(chunk.data().array(), is(bytes)); + assertThat(chunk.data()[0].array(), is(bytes)); } @Test @@ -48,12 +48,12 @@ public void testReleasing() { byte[] bytes = "urzatron".getBytes(StandardCharsets.UTF_8); AtomicBoolean ab = new AtomicBoolean(false); - DataChunk chunk = DataChunk.create(true, ByteBuffer.wrap(bytes), () -> ab.set(true)); + DataChunk chunk = DataChunk.create(true, () -> ab.set(true), ByteBuffer.wrap(bytes)); assertThat(chunk.bytes(), is(bytes)); assertThat(chunk.flush(), is(true)); assertThat(chunk.id(), not(0L)); - assertThat(chunk.data().array(), is(bytes)); + assertThat(chunk.data()[0].array(), is(bytes)); assertThat(chunk.isReleased(), is(false)); assertThat(ab.get(), is(false)); chunk.release(); @@ -69,7 +69,7 @@ public void testReleasingNoRunnable() { assertThat(chunk.bytes(), is(bytes)); assertThat(chunk.flush(), is(true)); assertThat(chunk.id(), not(0L)); - assertThat(chunk.data().array(), is(bytes)); + assertThat(chunk.data()[0].array(), is(bytes)); assertThat(chunk.isReleased(), is(false)); chunk.release(); assertThat(chunk.isReleased(), is(true)); diff --git a/common/http/src/test/java/io/helidon/common/http/ListContextTest.java b/common/http/src/test/java/io/helidon/common/http/ListContextTest.java deleted file mode 100644 index 39af8ed1c0b..00000000000 --- a/common/http/src/test/java/io/helidon/common/http/ListContextTest.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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.http; - -import java.util.Date; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * Tests {@link ListContextualRegistry} and {@link ContextualRegistry}. - */ -public class ListContextTest { - - @Test - public void create() { - assertThat(ContextualRegistry.create(), notNullValue()); - assertThat(ContextualRegistry.create(null), notNullValue()); - assertThat(ContextualRegistry.create(ContextualRegistry.create()), notNullValue()); - } - - @Test - public void registerAndGetLast() { - ContextualRegistry context = ContextualRegistry.create(); - assertThat(context.get(String.class), is(Optional.empty())); - assertThat(context.get(Integer.class), is(Optional.empty())); - context.register("aaa"); - assertThat(context.get(String.class), is(Optional.of("aaa"))); - assertThat(context.get(Integer.class), is(Optional.empty())); - context.register(1); - assertThat(context.get(String.class), is(Optional.of("aaa"))); - assertThat(context.get(Integer.class), is(Optional.of(1))); - assertThat(context.get(Object.class), is(Optional.of(1))); - context.register("bbb"); - assertThat(context.get(String.class), is(Optional.of("bbb"))); - assertThat(context.get(Object.class), is(Optional.of("bbb"))); - } - - @Test - public void registerAndGetLastClassifier() { - ContextualRegistry context = ContextualRegistry.create(); - String classifier = "classifier"; - assertThat(context.get(classifier, String.class), is(Optional.empty())); - assertThat(context.get(classifier, Integer.class), is(Optional.empty())); - context.register(classifier, "aaa"); - assertThat(context.get(classifier, String.class), is(Optional.of("aaa"))); - assertThat(context.get(String.class), is(Optional.empty())); - assertThat(context.get(classifier, Integer.class), is(Optional.empty())); - context.register(classifier, 1); - assertThat(context.get(classifier, String.class), is(Optional.of("aaa"))); - assertThat(context.get(classifier, Integer.class), is(Optional.of(1))); - assertThat(context.get(classifier, Object.class), is(Optional.of(1))); - context.register(classifier, "bbb"); - assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); - context.register("ccc"); - assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); - assertThat(context.get(classifier, Object.class), is(Optional.of("bbb"))); - assertThat(context.get(String.class), is(Optional.of("ccc"))); - } - - @Test - public void emptyParent() { - ContextualRegistry parent = ContextualRegistry.create(); - ContextualRegistry context = ContextualRegistry.create(parent); - assertThat(context.get(String.class), is(Optional.empty())); - context.register("aaa"); - assertThat(context.get(String.class), is(Optional.of("aaa"))); - } - - @Test - public void testParent() { - ContextualRegistry parent = ContextualRegistry.create(); - parent.register("ppp"); - ContextualRegistry context = ContextualRegistry.create(parent); - assertThat(context.get(String.class), is(Optional.of("ppp"))); - context.register(1); - assertThat(context.get(String.class), is(Optional.of("ppp"))); - context.register("aaa"); - assertThat(context.get(String.class), is(Optional.of("aaa"))); - assertThat(parent.get(String.class), is(Optional.of("ppp"))); - } - - @Test - public void testParentWithClassifier() { - String classifier = "classifier"; - ContextualRegistry parent = ContextualRegistry.create(); - parent.register(classifier, "ppp"); - ContextualRegistry context = ContextualRegistry.create(parent); - assertThat(context.get(classifier, String.class), is(Optional.of("ppp"))); - context.register(classifier, 1); - assertThat(context.get(classifier, String.class), is(Optional.of("ppp"))); - context.register(classifier, "aaa"); - assertThat(context.get(classifier, String.class), is(Optional.of("aaa"))); - assertThat(parent.get(classifier, String.class), is(Optional.of("ppp"))); - } - - @Test - public void testSupply() { - AtomicInteger counter = new AtomicInteger(0); - ContextualRegistry context = ContextualRegistry.create(); - context.register(1); - Date date = new Date(); - context.register(date); - context.register("aaa"); - context.supply(String.class, () -> { - counter.incrementAndGet(); - return "bbb"; - }); - context.register(2); - assertThat(context.get(Date.class), is(Optional.of(date))); - assertThat(counter.get(), is(0)); - assertThat(context.get(String.class), is(Optional.of("bbb"))); - assertThat(counter.get(), is(1)); - assertThat(context.get(String.class), is(Optional.of("bbb"))); - assertThat(counter.get(), is(1)); - assertThat(context.get(Date.class), is(Optional.of(date))); - assertThat(context.get(String.class), is(Optional.of("bbb"))); - assertThat(counter.get(), is(1)); - } - - @Test - public void testSupplyClassifier() { - String classifier = "classifier"; - AtomicInteger counter = new AtomicInteger(0); - ContextualRegistry context = ContextualRegistry.create(); - context.register(classifier, 1); - Date date = new Date(); - context.register(classifier, date); - context.register(classifier, "aaa"); - context.supply(classifier, String.class, () -> { - counter.incrementAndGet(); - return "bbb"; - }); - context.register(classifier, 2); - assertThat(context.get(classifier, Date.class), is(Optional.of(date))); - assertThat(counter.get(), is(0)); - assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); - assertThat(counter.get(), is(1)); - assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); - assertThat(counter.get(), is(1)); - assertThat(context.get(classifier, Date.class), is(Optional.of(date))); - assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); - assertThat(counter.get(), is(1)); - } -} diff --git a/common/http/src/test/java/io/helidon/common/http/ListContextualRegistryTest.java b/common/http/src/test/java/io/helidon/common/http/ListContextualRegistryTest.java deleted file mode 100644 index 442b3dddb38..00000000000 --- a/common/http/src/test/java/io/helidon/common/http/ListContextualRegistryTest.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * 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.http; - -import java.util.Date; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * Tests {@link ListContextualRegistry} and {@link ContextualRegistry}. - */ -public class ListContextualRegistryTest { - - @Test - public void create() { - assertThat(ContextualRegistry.create(), notNullValue()); - assertThat(ContextualRegistry.create(null), notNullValue()); - assertThat(ContextualRegistry.create(ContextualRegistry.create()), notNullValue()); - } - - @Test - public void registerAndGetLast() { - ContextualRegistry context = ContextualRegistry.create(); - assertThat(context.get(String.class), is(Optional.empty())); - assertThat(context.get(Integer.class), is(Optional.empty())); - context.register("aaa"); - assertThat(context.get(String.class), is(Optional.of("aaa"))); - assertThat(context.get(Integer.class), is(Optional.empty())); - context.register(1); - assertThat(context.get(String.class), is(Optional.of("aaa"))); - assertThat(context.get(Integer.class), is(Optional.of(1))); - assertThat(context.get(Object.class), is(Optional.of(1))); - context.register("bbb"); - assertThat(context.get(String.class), is(Optional.of("bbb"))); - assertThat(context.get(Object.class), is(Optional.of("bbb"))); - } - - @Test - public void registerAndGetLastClassifier() { - ContextualRegistry context = ContextualRegistry.create(); - String classifier = "classifier"; - assertThat(context.get(classifier, String.class), is(Optional.empty())); - assertThat(context.get(classifier, Integer.class), is(Optional.empty())); - context.register(classifier, "aaa"); - assertThat(context.get(classifier, String.class), is(Optional.of("aaa"))); - assertThat(context.get(String.class), is(Optional.empty())); - assertThat(context.get(classifier, Integer.class), is(Optional.empty())); - context.register(classifier, 1); - assertThat(context.get(classifier, String.class), is(Optional.of("aaa"))); - assertThat(context.get(classifier, Integer.class), is(Optional.of(1))); - assertThat(context.get(classifier, Object.class), is(Optional.of(1))); - context.register(classifier, "bbb"); - assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); - context.register("ccc"); - assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); - assertThat(context.get(classifier, Object.class), is(Optional.of("bbb"))); - assertThat(context.get(String.class), is(Optional.of("ccc"))); - } - - @Test - public void emptyParent() { - ContextualRegistry parent = ContextualRegistry.create(); - ContextualRegistry context = ContextualRegistry.create(parent); - assertThat(context.get(String.class), is(Optional.empty())); - context.register("aaa"); - assertThat(context.get(String.class), is(Optional.of("aaa"))); - } - - @Test - public void testParent() { - ContextualRegistry parent = ContextualRegistry.create(); - parent.register("ppp"); - ContextualRegistry context = ContextualRegistry.create(parent); - assertThat(context.get(String.class), is(Optional.of("ppp"))); - context.register(1); - assertThat(context.get(String.class), is(Optional.of("ppp"))); - context.register("aaa"); - assertThat(context.get(String.class), is(Optional.of("aaa"))); - assertThat(parent.get(String.class), is(Optional.of("ppp"))); - } - - @Test - public void testParentWithClassifier() { - String classifier = "classifier"; - ContextualRegistry parent = ContextualRegistry.create(); - parent.register(classifier, "ppp"); - ContextualRegistry context = ContextualRegistry.create(parent); - assertThat(context.get(classifier, String.class), is(Optional.of("ppp"))); - context.register(classifier, 1); - assertThat(context.get(classifier, String.class), is(Optional.of("ppp"))); - context.register(classifier, "aaa"); - assertThat(context.get(classifier, String.class), is(Optional.of("aaa"))); - assertThat(parent.get(classifier, String.class), is(Optional.of("ppp"))); - } - - @Test - public void testSupply() { - AtomicInteger counter = new AtomicInteger(0); - ContextualRegistry context = ContextualRegistry.create(); - context.register(1); - Date date = new Date(); - context.register(date); - context.register("aaa"); - context.supply(String.class, () -> { - counter.incrementAndGet(); - return "bbb"; - }); - context.register(2); - assertThat(context.get(Date.class), is(Optional.of(date))); - assertThat(counter.get(), is(0)); - assertThat(context.get(String.class), is(Optional.of("bbb"))); - assertThat(counter.get(), is(1)); - assertThat(context.get(String.class), is(Optional.of("bbb"))); - assertThat(counter.get(), is(1)); - assertThat(context.get(Date.class), is(Optional.of(date))); - assertThat(context.get(String.class), is(Optional.of("bbb"))); - assertThat(counter.get(), is(1)); - } - - @Test - public void testSupplyClassifier() { - String classifier = "classifier"; - AtomicInteger counter = new AtomicInteger(0); - ContextualRegistry context = ContextualRegistry.create(); - context.register(classifier, 1); - Date date = new Date(); - context.register(classifier, date); - context.register(classifier, "aaa"); - context.supply(classifier, String.class, () -> { - counter.incrementAndGet(); - return "bbb"; - }); - context.register(classifier, 2); - assertThat(context.get(classifier, Date.class), is(Optional.of(date))); - assertThat(counter.get(), is(0)); - assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); - assertThat(counter.get(), is(1)); - assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); - assertThat(counter.get(), is(1)); - assertThat(context.get(classifier, Date.class), is(Optional.of(date))); - assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); - assertThat(counter.get(), is(1)); - } -} diff --git a/common/metrics/pom.xml b/common/metrics/pom.xml deleted file mode 100644 index 3fd541fe7d0..00000000000 --- a/common/metrics/pom.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - 4.0.0 - - io.helidon.common - helidon-common-project - 2.0.0-SNAPSHOT - - helidon-common-metrics - - Helidon Common Metrics - - - Helidon Metrics common definitions - - - - - io.helidon.config - helidon-config - - - org.eclipse.microprofile.metrics - microprofile-metrics-api - provided - - - diff --git a/common/metrics/src/main/java/io/helidon/common/metrics/InternalBridge.java b/common/metrics/src/main/java/io/helidon/common/metrics/InternalBridge.java deleted file mode 100644 index eddef8d9803..00000000000 --- a/common/metrics/src/main/java/io/helidon/common/metrics/InternalBridge.java +++ /dev/null @@ -1,771 +0,0 @@ -/* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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.metrics; - -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.SortedMap; -import java.util.SortedSet; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import io.helidon.config.Config; - -import org.eclipse.microprofile.metrics.Counter; -import org.eclipse.microprofile.metrics.Gauge; -import org.eclipse.microprofile.metrics.Histogram; -import org.eclipse.microprofile.metrics.Meter; -import org.eclipse.microprofile.metrics.Metric; -import org.eclipse.microprofile.metrics.MetricType; -import org.eclipse.microprofile.metrics.Timer; - -/** - * Internal abstraction layer for MicroProfile 1.1 and 2.0. - *

- * Only Helidon internal clients of metrics should use this interface. Other - * clients should use Helidon BOMs or bundles which will use the appropriate - * versions of Helidon metrics and MicroProfile Metrics. - *

- * Implementations provide for getting instances of these features: - *

    - *
  • registry factory - *
  • metric registry - *
  • metric ID - *
  • metadata builder - *
  • metadata - *
- * This interface is organized accordingly. Each of these exposes a factory and - * an instance of that factory. (The metadata builder from MP Metrics 2.0 is the - * factory for metadata, and therefore is nested under metadata in this - * interface. ) The top level of the bridge also exposes a few convenience - * methods for getting new instances of a version-specific implementations of the types. - */ -public interface InternalBridge { - - /** - * Returns the singleton instance of the bridge. - */ - InternalBridge INSTANCE = Loader.internalBridge(); - - /** - * - * @return the singleton metric ID factory - */ - MetricID.Factory getMetricIDFactory(); - - /** - * - * @return the singleton metadata builder factory - */ - Metadata.MetadataBuilder.Factory getMetadataBuilderFactory(); - - /** - * - * @return the singleton registry factory - */ - MetricRegistry.RegistryFactory getRegistryFactory(); - - /** - * - * @return a metadata builder - */ - static Metadata.MetadataBuilder newMetadataBuilder() { - return INSTANCE.getMetadataBuilderFactory().newMetadataBuilder(); - } - - /** - * Creates a new {@code RegistryFactory} with the default configuration, as - * exposed through the {@code InternalBridge.RegistryFactory} interface. - * - * @return the new {@code RegistryFactory} - */ - MetricRegistry.RegistryFactory createRegistryFactory(); - - /** - * Creates a new {@code RegistryFactory} with the specified configuration, - * as exposed through the {@code InternalBridge.RegistryFactory} interface. - * - * @param config the Helidon {@link Config} to use in initializing the - * factory. - * @return the new {@code RegistryFactory} - */ - MetricRegistry.RegistryFactory createRegistryFactory(Config config); - - /** - * Abstraction of the {@code MetricRegistry} behavior used by internal - * Helidon clients. - *

- * The exposed methods use version-neutral abstractions for - * {@code Metadata}, {@code MetricID}, and {@code Tag} which are used by - * MicroProfile Metrics. Some methods have {@code bridge} in their names because - * the corresponding MicroProfile Metrics methods changed return type but - * kept the same signature from 1.1 to 2.0, so here they need distinct names - * to distinguish them from the spec-prescribed methods (that do not use the - * version-neutral constructs). - */ - public interface MetricRegistry { - - /** - * Finds or creates a new {@code Counter} using the specified - * version-neutral {@code Metadata}. - * - * @param metadata used in locating and, if needed, building the counter - * @return the {@code Counter} - */ - Counter counter(Metadata metadata); - - /** - * Finds or creates a new {@code} Counter using the specified - * version-neutral {@code Metadata} and version-neutral {@code Tag}s. - * - * @param metadata used in locating and, if needed, building the counter - * @param tags used in locating and, if needed, building the counter - * @return the {@code Counter} - */ - Counter counter(Metadata metadata, Map tags); - - /** - * Finds or creates a new {@code Counter} using the specified name. - * - * @param name name for the new {@code Counter} - * @return the {@code Counter} - */ - Counter counter(String name); - - /** - * Finds or creates a new {@code Meter} using the specified - * version-neutral {@code Metadata}. - * - * @param metadata used in locating and, if needed, building the meter - * @return the {@code Meter} - */ - Meter meter(Metadata metadata); - - /** - * Finds or creates a new {@code Meter} using the specified - * version-neutral {@code Metadata} and version-neutral {@code Tag}s. - * - * @param metadata used in locating and, if needed, building the meter - * @param tags used in locating and, if needed, building the meter - * @return the {@code Meter} - */ - Meter meter(Metadata metadata, Map tags); - - /** - * Finds or creates a new {@code Meter} using the specified name. - * - * @param name used in locating and, if needed, building the meter - * @return the {@code Meter} - */ - Meter meter(String name); - - /** - * Finds or creates a new {@code Histogram} using the specified - * version-neutral {@code Metadata}. - * - * @param metadata used in locating and, if needed, building the - * histogram - * @return the {@code Histogram} - */ - Histogram histogram(Metadata metadata); - - /** - * Finds or creates a new {@code Histogram} using the specified - * version-neutral {@code Metadata} and version-neutral {@code Tag}s. - * - * @param metadata used in locating and, if needed, building the - * histogram - * @param tags used in locating and, if needed, building the histogram - * @return the {@code Histogram} - */ - Histogram histogram(Metadata metadata, Map tags); - - /** - * Finds or creates a new {@code Histogram} using the specified - * {@code Metadata}. - * - * @param name used in locating and, if needed, building the histogram - * @return the {@code Histogram} - */ - Histogram histogram(String name); - - /** - * Finds or creates a new {@code Timer} using the specified - * version-neutral {@code Metadata}. - * - * @param metadata used in locating and, if needed, building the timer - * @return the {@code Timer} - */ - Timer timer(Metadata metadata); - - /** - * Finds or creates a new {@code Timer} using the specified - * version-neutral {@code Metadata} and version-neutral {@code Tag}s. - * - * @param metadata used in locating and, if needed, building the timer - * @param tags used in locationg and, if needed, building the timer - * @return the {@code Timer} - */ - Timer timer(Metadata metadata, Map tags); - - /** - * Finds or creates a new {@code Timer} using the specified name. - * - * @param name used in locating and, if needed, building the timer - * @return the {@code Timer} - */ - Timer timer(String name); - - /** - * Returns all metrics from the registry as a map of version-neutral - * {@link MetricID}s to {@code Metric}s. - * - * @return the metrics - */ - Map getBridgeMetrics(); - - /** - * Returns all metrics from the registry as a map of version-neutral - * {@link MetricID}s to {@code Metric}s, filtered by the provided - * {@link Predicate}. - * - * @param predicate for selecting which metrics to include in the result - * @return the metrics matching the criteria expressed in the predicate - */ - Map getBridgeMetrics( - Predicate> predicate); - - /** - * Returns an {@link Optional} of the {@code MetricID} and - * {@link Metric} of the metric matching the given name. If multiple - * metrics match on the name (this can happen in MP Metrics 2.0 if the - * metrics were created with different tags) then the method returns the - * first metric with that name, if any. - * - * @param metricName name of the metric to find - * @return {@code Optional} of a {@code Map.Entry} for the matching ID - * and metric - */ - Optional> getBridgeMetric(String metricName); - - /** - * Returns the names of all metrics in the registry. - * - * @return a {@code Set} containing the names - */ - SortedSet getNames(); - - /** - * Returns all {@code Counter} metrics in the registry as a map of - * version-neutral {@link MetricID} to {@link Metric} entries. - * - * @return a map of all counters - */ - SortedMap getBridgeCounters(); - - /** - * Returns all {@code Gauge} metrics in the registry as a map of - * version-neutral {@link MetricID} to {@link Metric} entries. - * - * @return a map of all gauges - */ - SortedMap getBridgeGauges(); - - /** - * Returns all {@code Histogram} metrics in the registry as a map of - * version-neutral {@link MetricID} to {@link Metric} entries. - * - * @return a map of all histograms - */ - SortedMap getBridgeHistograms(); - - /** - * Returns all {@code Meter} metrics in the registry as a map of - * version-neutral {@link MetricID} to {@link Metric} entries. - * - * @return a map of all meters - */ - SortedMap getBridgeMeters(); - - /** - * Returns all {@code Timer} metrics in the registry as a map of - * version-neutral {@link MetricID} to {@link Metric} entries. - * - * @return a map of all timers - */ - SortedMap getBridgeTimers(); - - /** - * Registers a new metric using the specified version-neutral - * {@link Metadata} and the typed metric itself. - * - * @param the metric type - * @param metadata the metadata used in registering the metric - * @param metric the metric to register - * @return the registered metric - * @throws IllegalArgumentException if a metric with the same name but - * inconsistent metadata is already registered - */ - T register(Metadata metadata, T metric) throws IllegalArgumentException; - - /** - * Registers a new metric using the specified version-neutral - * {@link MetricID} and the typed metric itself. - * - * @param the metric type - * @param metricID the metric ID to be used in registering the metric - * @param metric the metric to register - * @return the registered metric - * @throws IllegalArgumentException if a metric with the same identify - * but inconsistent metadata is already registered - */ - T register(MetricID metricID, T metric) throws IllegalArgumentException; - - /** - * Removes the metrics with matching name from the registry. - * - * @param name name of the metric - * @return true if a matching metric was removed; false otherwise - */ - boolean remove(String name); - - /** - * Abstraction of the {@code RegistryFactory} behavior used by internal - * Helidon clients. - *

- * Using the name {@code RegistryFactory} here instead of just {@code Factory} - * should simplify changing the client code to use only the MP Metrics 2.0 - * implementation later. - */ - public interface RegistryFactory { - - /** - * The factory's singleton instance. - */ - RegistryFactory INSTANCE = Loader.registryFactory(); - - /** - * Returns the MicroProfile metric {@code MetricRegistry} of the - * indicated registry type typed as the internal abstraction. - * - * @param type registry type selected - * @return {@code MetricRegistry} of the selected type - */ - MetricRegistry getBridgeRegistry(org.eclipse.microprofile.metrics.MetricRegistry.Type type); - } - } - - /** - * Version-neutral abstraction of a metric identifier. - *

- * Note that for a metric with tags, the tags are ALWAYS present in the - * neutral {@code MetricID}. We want to encourage the internal clients to - * use the newer programming style, retrieving tags from the ID rather than - * the metadata (where it was stored in MP Metrics 1.1). - */ - public interface MetricID extends Comparable { - - /** - * - * @return the name from the identifier - */ - String getName(); - - /** - * - * @return the tags from the identifier, as a {@code Map} - */ - Map getTags(); - - /** - * Provides the tags as a {@code List}. The returned {@code Tag} objects - * are separate from those associated with the ID so changes to the tags - * made by the caller do not perturb the original ID. - * - * @return the {@code Tag}s - */ - default List getTagsAsList() { - return getTags().entrySet().stream() - .map(entry -> Tag.newTag(entry.getKey(), entry.getValue())) - .collect(Collectors.toList()); - } - - /** - * Describes the tags as a single string: name1=value1,name2=value2,... - * - * @return {@code String} containing the tags - */ - default String getTagsAsString() { - return getTags().entrySet().stream() - .map((entry) -> String.format("%s=%s", entry.getKey(), entry.getValue())) - .collect(Collectors.joining(",")); - } - - /** - * Compares this instance to another object (per {@code Comparable}. - * - * @param o the other object to compare to - * @return -1, 0, +1 depending on whether this instance is less than, - * equal to, or greater than the other object. - */ - @Override - default int compareTo(MetricID o) { - int result = getName().compareTo(Objects.requireNonNull(o).getName()); - if (result != 0) { - return result; - } - result = getTags().size() - o.getTags().size(); - if (result == 0) { - Iterator> thisIterator = getTags().entrySet().iterator(); - Iterator> otherIterator = o.getTags().entrySet().iterator(); - while (thisIterator.hasNext() && otherIterator.hasNext()) { - Map.Entry thisEntry = thisIterator.next(); - Map.Entry otherEntry = otherIterator.next(); - result = thisEntry.getKey().compareTo(otherEntry.getKey()); - if (result != 0) { - return result; - } else { - result = thisEntry.getValue().compareTo(otherEntry.getValue()); - if (result != 0) { - return result; - } - } - } - } - return result; - } - - /** - * Creates new {@code MetricID} instances. - */ - public interface Factory { - - /** - * Singleton instance of the factory. - */ - Factory INSTANCE = Loader.metricIDFactory(); - - /** - * Creates a version-neutral {@code MetricID} initialized with the - * specified name and any global tags. - * - * @param name name of the metric to create - * @return the new {@code MetricID} - */ - InternalBridge.MetricID newMetricID(String name); - - /** - * Creates a version-neutral {@code MetricID} initializes with the - * specified name and tags. - * - * @param name name of the metric to create - * @param tags tags to associated with the new ID - * @return the new {@code MetricID} - */ - InternalBridge.MetricID newMetricID(String name, Map tags); - } - } - - /** - * Version-neutral abstraction of metric metadata. - *

- * Although this interface supports tags, if you are using MicroProfile - * Metrics 2.0 or later the system ignores tags associated with metadata. - */ - public interface Metadata { - - /** - * - * @return the metric name stored in the metadata - */ - String getName(); - - /** - * - * @return the display name - */ - String getDisplayName(); - - /** - * - * @return an {@code Optional} of the metadata description - */ - Optional getDescription(); - - /** - * - * @return the metric type as a {@code String} - */ - String getType(); - - /** - * - * @return the metric type as a MicroProfile Metrics {link MetricType} - */ - MetricType getTypeRaw(); - - /** - * - * @return an {@code Optional} of the unit associated with this metadata - */ - Optional getUnit(); - - /** - * - * @return whether metrics described by this metadata are reusable or - * not - */ - boolean isReusable(); - - /** - * Returns the tags associated with the metadata. - *

- * Note that if you are using MicroProfile Metrics 2.0 and later the - * tags associated with this version-neutral metadata are ignored. - * - * @return tags - */ - Map getTags(); - - /** - * Prepares a version-neutral {@link Metadata} instance using the - * specified values, avoiding the need to create and act on a builder. - *

- * Note that although this method accepts tags, if you are using - * MicroProfile Metrics 2.0 or later the returned metadata will not - * include the tags. - * - * @param name name for the metrics associated with the metadata - * @param displayName display name - * @param description description of the metric - * @param type {@code MetricType} of the metric - * @param unit unit that applies to the metric - * @param isReusable whether or not metrics based on this metadata - * should be reusable - * @param tags name/value pairs representing tags - * @return the prepared version-neutral {@code Metadata} - */ - static InternalBridge.Metadata newMetadata(String name, String displayName, String description, - MetricType type, String unit, boolean isReusable, Map tags) { - final MetadataBuilder builder = MetadataBuilder.Factory.INSTANCE.newMetadataBuilder() - .withName(name) - .withDescription(description) - .withDisplayName(displayName) - .withType(type) - .withTags(tags) - .withUnit(unit); - return (isReusable ? builder.reusable() : builder.notReusable()).build(); - } - - /** - * Prepares a version-neutral {@link Metadata} instance using the - * specified values, avoiding the need to create and act on a builder. - *

- * Note that although this method accepts tags, if you are using - * MicroProfile Metrics 2.0 or later the returned metadata will not - * include the tags. - *

- * Also note that whether the metadata is reusable relies on the - * underlying MicroProfile Metrics version you are using. - * - * @param name name for the metrics associated with the metadata - * @param displayName display name - * @param description description of the metric - * @param type {@code MetricType} of the metric - * @param unit unit that applies to the metric - * @param tags name/value pairs representing tags - * @return the prepared version-neutral {@code Metadata} - */ - static InternalBridge.Metadata newMetadata(String name, String displayName, String description, - MetricType type, String unit, Map tags) { - return MetadataBuilder.Factory.INSTANCE.newMetadataBuilder() - .withName(name) - .withDescription(description) - .withDisplayName(displayName) - .withType(type) - .withTags(tags) - .withUnit(unit) - .build(); - } - - /** - * Prepares a version-neutral {@link Metadata} instance using the - * specified values, avoiding the need to create and act on a builder. - *

- * Note that whether the metadata is reusable relies on the underlying - * MicroProfile Metrics version you are using. - * - * @param name name for the metrics associated with the metadata - * @param displayName display name - * @param description description of the metric - * @param type {@code MetricType} of the metric - * @param unit unit that applies to the metric - * @return the prepared version-neutral {@code Metadata} - */ - static InternalBridge.Metadata newMetadata(String name, String displayName, String description, - MetricType type, String unit) { - return MetadataBuilder.Factory.INSTANCE.newMetadataBuilder() - .withName(name) - .withDescription(description) - .withDisplayName(displayName) - .withType(type) - .withUnit(unit) - .build(); - } - - /** - * Fluent-style builder for version-neutral {@link Metadata}. - *

- * The name is from MP Metrics but is actually the factory for {@code Metadata} - * in the design of the bridge. - * - */ - public interface MetadataBuilder { - - /** - * Sets the name. - * - * @param name name to be used in the metadata; cannot be null - * @return the same builder - */ - MetadataBuilder withName(String name); - - /** - * Sets the display name. - * - * @param displayName display name to be used in the metadata; - * cannot be null - * @return the same builder - */ - MetadataBuilder withDisplayName(String displayName); - - /** - * Sets the description. - * - * @param description description to be used in the metadata; cannot - * be null - * @return the same builder - */ - MetadataBuilder withDescription(String description); - - /** - * Sets the metric type. - * - * @param type {@link MetricType} to be used in the metadata; cannot - * be null - * @return the same builder - */ - MetadataBuilder withType(MetricType type); - - /** - * Sets the unit. - * - * @param unit unit to be used in the metadata; cannot be null - * @return the same builder - */ - MetadataBuilder withUnit(String unit); - - /** - * Sets that the resulting metadata will be reusable. - * - * @return the same builder - */ - MetadataBuilder reusable(); - - /** - * Sets that the resulting metadata will not be reusable. - * - * @return the same builder - */ - MetadataBuilder notReusable(); - - /** - * Sets the tags. - *

- * Note that when you use MicroProfile Metrics 2.0 or later, tags - * associated with metadata are ignored except within the metadata - * itself. - * - * @param tags map conveying the tags to be used in the metadata; - * @return the same builder - */ - MetadataBuilder withTags(Map tags); - - /** - * Creates a {@link Metadata} instance using the values set by - * invocations of the various {@code withXXX} methods. - * - * @return the version-neutral {@code Metadata} - * @throws IllegalStateException if the name was never set - */ - InternalBridge.Metadata build(); - - /** - * Factory for {@code MetadataBuilder} instances. - */ - public interface Factory { - - /** - * The factory's singleton instance. - */ - Factory INSTANCE = Loader.metadataBuilderFactory(); - - /** - * Returns a new version-specific {@code MetadataBuilder} that - * implements the neutral interface. - * - * @return the builder - */ - MetadataBuilder newMetadataBuilder(); - } - } - - } - - /** - * Version-neutral representation of a tag. - */ - public interface Tag { - - /** - * Creates a new version-neutral tag with the specified name and value. - * - * @param name name for the new tag - * @param value value for the new tag - * @return the new version-neutral tag - */ - static Tag newTag(String name, String value) { - return new InternalTagImpl(name, value); - } - - /** - * - * @return the tag's name - */ - String getTagName(); - - /** - * - * @return the tag's value - */ - String getTagValue(); - } - -} diff --git a/common/metrics/src/main/java/io/helidon/common/metrics/InternalTagImpl.java b/common/metrics/src/main/java/io/helidon/common/metrics/InternalTagImpl.java deleted file mode 100644 index bc88236fdb4..00000000000 --- a/common/metrics/src/main/java/io/helidon/common/metrics/InternalTagImpl.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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.metrics; - -import java.util.Objects; - -/** - * Version-neutral implementation of {@code Tag} expressing a name/value pair. - *

- * To create a new instance of this class, use the - * {@link InternalBridge.Tag#newTag(java.lang.String, java.lang.String)} method. - */ -class InternalTagImpl implements InternalBridge.Tag { - - private final String name; - private final String value; - - /** - * Creates a new tag. - * - * @param name used for the tag - * @param value used for the tag - */ - InternalTagImpl(String name, String value) { - this.name = name; - this.value = value; - } - - /** - * - * @return the name of the tag - */ - @Override - public String getTagName() { - return name; - } - - /** - * - * @return the value of the tag - */ - @Override - public String getTagValue() { - return value; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 59 * hash + Objects.hashCode(this.name); - hash = 59 * hash + Objects.hashCode(this.value); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final InternalTagImpl other = (InternalTagImpl) obj; - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!Objects.equals(this.value, other.value)) { - return false; - } - return true; - } - - @Override - public String toString() { - return String.format("Tag{%s=%s}", name, value); - } - -} diff --git a/common/metrics/src/main/java/io/helidon/common/metrics/Loader.java b/common/metrics/src/main/java/io/helidon/common/metrics/Loader.java deleted file mode 100644 index 3cafec5c80f..00000000000 --- a/common/metrics/src/main/java/io/helidon/common/metrics/Loader.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or 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.metrics; - -import java.util.Iterator; -import java.util.ServiceLoader; - -/** - * Uses the Java service loader mechanism to find an implementation of the - * internal bridge and offer access to the various factories that implementation - * provides. - */ -class Loader { - private static final io.helidon.common.metrics.InternalBridge BRIDGE = loadInternalBridge(); - - static io.helidon.common.metrics.InternalBridge internalBridge() { - return BRIDGE; - } - - static io.helidon.common.metrics.InternalBridge.MetricRegistry.RegistryFactory registryFactory() { - return BRIDGE.getRegistryFactory(); - } - - static io.helidon.common.metrics.InternalBridge.MetricID.Factory metricIDFactory() { - return BRIDGE.getMetricIDFactory(); - } - - static io.helidon.common.metrics.InternalBridge.Metadata.MetadataBuilder.Factory metadataBuilderFactory() { - return BRIDGE.getMetadataBuilderFactory(); - } - - private static io.helidon.common.metrics.InternalBridge loadInternalBridge() { - for (Iterator it = - ServiceLoader.load(io.helidon.common.metrics.InternalBridge.class).iterator(); - it.hasNext();) { - return it.next(); - } - throw new RuntimeException("Could not find implementation of bridge " - + io.helidon.common.metrics.InternalBridge.class.getName() + " to load"); - } - - private Loader() { - } - -} diff --git a/common/metrics/src/main/java/io/helidon/common/metrics/package-info.java b/common/metrics/src/main/java/io/helidon/common/metrics/package-info.java deleted file mode 100644 index 43ca98ba1cf..00000000000 --- a/common/metrics/src/main/java/io/helidon/common/metrics/package-info.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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. - * - */ - -/** - * A collection of version-neutral interfaces (and some implementations where they - * apply to all versions) of constructs used in MicroProfile Metrics. - *

- * Note: Only Helidon internal clients of metrics should use the classes and - * interfaces in this package. The abstracting interfaces expose only the bare - * minimum surface area of their version-specific counterparts that are used by - * the various internal Helidon clients of metrics. - */ -package io.helidon.common.metrics; diff --git a/common/pom.xml b/common/pom.xml index d00f59cb8d6..16d13102934 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -1,7 +1,7 @@ + + + + + + + + + diff --git a/config/config/src/main/java/io/helidon/config/Config.java b/config/config/src/main/java/io/helidon/config/Config.java index 13100181ccb..ccae99f06f4 100644 --- a/config/config/src/main/java/io/helidon/config/Config.java +++ b/config/config/src/main/java/io/helidon/config/Config.java @@ -374,6 +374,22 @@ static Builder builder() { return new BuilderImpl(); } + /** + * Creates a new {@link Config} loaded from the specified {@link ConfigSource}s. + * No other sources will be included. + * + * @param configSources ordered list of configuration sources + * @return new instance of {@link Config} + * @see #builder(Supplier[]) + */ + @SafeVarargs + static Config just(Supplier... configSources) { + return builder(configSources) + .disableEnvironmentVariablesSource() + .disableSystemPropertiesSource() + .build(); + } + /** * Returns the {@code Context} instance associated with the current * {@code Config} node that allows the application to access the last loaded diff --git a/dbclient/common/src/main/java/io/helidon/dbclient/common/AbstractStatement.java b/dbclient/common/src/main/java/io/helidon/dbclient/common/AbstractStatement.java index 48536116810..e4514e5f46a 100644 --- a/dbclient/common/src/main/java/io/helidon/dbclient/common/AbstractStatement.java +++ b/dbclient/common/src/main/java/io/helidon/dbclient/common/AbstractStatement.java @@ -19,13 +19,10 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import io.helidon.common.context.Context; -import io.helidon.common.context.Contexts; import io.helidon.common.mapper.MapperManager; -import io.helidon.dbclient.DbInterceptor; -import io.helidon.dbclient.DbInterceptorContext; +import io.helidon.common.reactive.Single; +import io.helidon.dbclient.DbClientServiceContext; import io.helidon.dbclient.DbMapperManager; import io.helidon.dbclient.DbStatement; import io.helidon.dbclient.DbStatementType; @@ -38,89 +35,54 @@ */ public abstract class AbstractStatement, R> implements DbStatement { + private final DbClientContext clientContext; + private final DbStatementContext statementContext; + private ParamType paramType = ParamType.UNKNOWN; private StatementParameters parameters; - private final DbStatementType dbStatementType; - private final String statementName; - private final String statement; - private final DbMapperManager dbMapperManager; - private final MapperManager mapperManager; - private final InterceptorSupport interceptors; /** * Statement that handles parameters. * - * @param dbStatementType type of this statement - * @param statementName name of this statement - * @param statement text of this statement - * @param dbMapperManager db mapper manager to use when mapping types to parameters - * @param mapperManager mapper manager to use when mapping results - * @param interceptors interceptors to be executed + * @param statementContext database statement configuration and context */ - protected AbstractStatement(DbStatementType dbStatementType, - String statementName, - String statement, - DbMapperManager dbMapperManager, - MapperManager mapperManager, - InterceptorSupport interceptors) { - this.dbStatementType = dbStatementType; - this.statementName = statementName; - this.statement = statement; - this.dbMapperManager = dbMapperManager; - this.mapperManager = mapperManager; - this.interceptors = interceptors; + protected AbstractStatement(DbStatementContext statementContext) { + this.statementContext = statementContext; + this.clientContext = statementContext.clientContext(); } @Override public R execute() { CompletableFuture queryFuture = new CompletableFuture<>(); CompletableFuture statementFuture = new CompletableFuture<>(); - DbInterceptorContext dbContext = DbInterceptorContext.create(dbType()) + DbClientServiceContext dbContext = DbClientServiceContext.create(dbType()) .resultFuture(queryFuture) .statementFuture(statementFuture); update(dbContext); - CompletionStage dbContextFuture = invokeInterceptors(dbContext); + Single dbContextFuture = clientContext.invokeServices(dbContext); return doExecute(dbContextFuture, statementFuture, queryFuture); } - /** - * Invoke all interceptors. - * - * @param dbContext initial interceptor context - * @return future with the result of interceptors processing - */ - CompletionStage invokeInterceptors(DbInterceptorContext dbContext) { - CompletableFuture result = CompletableFuture.completedFuture(dbContext); - - dbContext.context(Contexts.context().orElseGet(Context::create)); - - for (DbInterceptor interceptor : interceptors.interceptors(statementType(), statementName())) { - result = result.thenCompose(interceptor::statement); - } - - return result; - } - /** * Type of this statement. * * @return statement type */ protected DbStatementType statementType() { - return dbStatementType; + return statementContext.statementType(); } /** * Execute the statement against the database. * - * @param dbContext future that completes after all interceptors are invoked + * @param dbContext future that completes after all services are invoked * @param statementFuture future that should complete when the statement finishes execution * @param queryFuture future that should complete when the result set is fully read (if one exists), * otherwise complete same as statementFuture * @return result of this db statement. */ - protected abstract R doExecute(CompletionStage dbContext, + protected abstract R doExecute(Single dbContext, CompletableFuture statementFuture, CompletableFuture queryFuture); @@ -131,6 +93,15 @@ protected abstract R doExecute(CompletionStage dbContext, */ protected abstract String dbType(); + /** + * Context of the DB client. + * + * @return context with access to client wide configuration and runtime + */ + public DbClientContext clientContext() { + return clientContext; + } + @Override public S params(List parameters) { Objects.requireNonNull(parameters, "Parameters cannot be null (may be an empty list)"); @@ -191,7 +162,7 @@ protected ParamType paramType() { * @return mapper manager for DB types */ protected DbMapperManager dbMapperManager() { - return dbMapperManager; + return clientContext.dbMapperManager(); } /** @@ -200,7 +171,7 @@ protected DbMapperManager dbMapperManager() { * @return generic mapper manager */ protected MapperManager mapperManager() { - return mapperManager; + return clientContext.mapperManager(); } /** @@ -231,7 +202,7 @@ protected List indexedParams() { * @return name of this statement (never null, may be generated) */ protected String statementName() { - return statementName; + return statementContext.statementName(); } /** @@ -240,23 +211,23 @@ protected String statementName() { * @return text of this statement */ protected String statement() { - return statement; + return statementContext.statement(); } /** - * Update the interceptor context with the statement name, statement and + * Update the client service context with the statement name, statement and * statement parameters. * - * @param dbContext interceptor context + * @param dbContext client service context */ - protected void update(DbInterceptorContext dbContext) { - dbContext.statementName(statementName); + protected void update(DbClientServiceContext dbContext) { + dbContext.statementName(statementContext.statementName()); initParameters(ParamType.INDEXED); if (paramType == ParamType.NAMED) { - dbContext.statement(statement, parameters.namedParams()); + dbContext.statement(statementContext.statement(), parameters.namedParams()); } else { - dbContext.statement(statement, parameters.indexedParams()); + dbContext.statement(statementContext.statement(), parameters.indexedParams()); } dbContext.statementType(statementType()); } @@ -279,13 +250,13 @@ private void initParameters(ParamType type) { switch (type) { case NAMED: this.paramType = ParamType.NAMED; - this.parameters = new NamedStatementParameters(dbMapperManager); + this.parameters = new NamedStatementParameters(clientContext.dbMapperManager()); break; case INDEXED: case UNKNOWN: default: this.paramType = ParamType.INDEXED; - this.parameters = new IndexedStatementParameters(dbMapperManager); + this.parameters = new IndexedStatementParameters(clientContext.dbMapperManager()); break; } } diff --git a/dbclient/common/src/main/java/io/helidon/dbclient/common/DbClientContext.java b/dbclient/common/src/main/java/io/helidon/dbclient/common/DbClientContext.java new file mode 100644 index 00000000000..14eebd42081 --- /dev/null +++ b/dbclient/common/src/main/java/io/helidon/dbclient/common/DbClientContext.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.dbclient.common; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import io.helidon.common.context.Context; +import io.helidon.common.context.Contexts; +import io.helidon.common.mapper.MapperManager; +import io.helidon.common.reactive.Single; +import io.helidon.dbclient.DbClientService; +import io.helidon.dbclient.DbClientServiceContext; +import io.helidon.dbclient.DbMapperManager; +import io.helidon.dbclient.DbStatements; + +/** + * Context of the whole client. + *

+ * This instance holds configuration and runtimes that are shared by any exec within this client runtime. + */ +public class DbClientContext { + private final DbMapperManager dbMapperManager; + private final MapperManager mapperManager; + private final List clientServices; + private final DbStatements statements; + + /** + * Create an instance from builder. + * + * @param builder the builder base your builder must extend + */ + protected DbClientContext(BuilderBase builder) { + this.dbMapperManager = builder.dbMapperManager; + this.mapperManager = builder.mapperManager; + this.clientServices = builder.clientServices; + this.statements = builder.statements; + } + + /** + * Create a new builder for context. + * + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Invoke all configured client services and return a single that completes once all the + * client services complete. + * + * @param dbContext context for client services + * @return a single with the same or modified client service context + */ + public Single invokeServices(DbClientServiceContext dbContext) { + CompletableFuture result = CompletableFuture.completedFuture(dbContext); + + dbContext.context(Contexts.context().orElseGet(Context::create)); + + for (DbClientService service : clientServices) { + result = result.thenCompose(service::statement); + } + + return Single.create(result); + } + + /** + * Configured statements. + * + * @return statements + */ + public DbStatements statements() { + return statements; + } + + /** + * Configured DB Mapper manager. + * + * @return DB mapper manager + */ + public DbMapperManager dbMapperManager() { + return dbMapperManager; + } + + /** + * Configured mapper manager. + * + * @return mapper manager + */ + public MapperManager mapperManager() { + return mapperManager; + } + + /** + * Fluent API builder for {@link io.helidon.dbclient.common.DbClientContext}. + */ + public static final class Builder extends BuilderBase implements io.helidon.common.Builder { + @Override + public DbClientContext build() { + return new DbClientContext(this); + } + } + + /** + * A common base for builders for classes that want to extend {@link io.helidon.dbclient.common.DbClientContext}. + * + * @param type of the builder extending this builder, to keep fluent API + */ + public static class BuilderBase> { + @SuppressWarnings("unchecked") + private final T me = (T) this; + + private DbMapperManager dbMapperManager; + private MapperManager mapperManager; + private List clientServices; + private DbStatements statements; + + /** + * No-op constructor. + */ + protected BuilderBase() { + } + + /** + * Configure the DB mapper manager to use. + * + * @param dbMapperManager DB mapper manager + * @return updated builder instance + */ + public T dbMapperManager(DbMapperManager dbMapperManager) { + this.dbMapperManager = dbMapperManager; + return me; + } + + /** + * Configure the mapper manager to use. + * + * @param mapperManager mapper manager + * @return updated builder instance + */ + public T mapperManager(MapperManager mapperManager) { + this.mapperManager = mapperManager; + return me; + } + + /** + * Configure the client services to use. + * + * @param clientServices client service list + * @return updated builder instance + */ + public T clientServices(List clientServices) { + this.clientServices = clientServices; + return me; + } + + /** + * Configure the db statements to use. + * + * @param statements statements + * @return updated builder instance + */ + public T statements(DbStatements statements) { + this.statements = statements; + return me; + } + } +} diff --git a/dbclient/common/src/main/java/io/helidon/dbclient/common/DbClientServiceBase.java b/dbclient/common/src/main/java/io/helidon/dbclient/common/DbClientServiceBase.java new file mode 100644 index 00000000000..1cab48b5c9b --- /dev/null +++ b/dbclient/common/src/main/java/io/helidon/dbclient/common/DbClientServiceBase.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.dbclient.common; + +import java.util.EnumSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import io.helidon.common.reactive.Single; +import io.helidon.config.Config; +import io.helidon.dbclient.DbClientService; +import io.helidon.dbclient.DbClientServiceContext; +import io.helidon.dbclient.DbStatementType; + +/** + * A base implementation of a client service that supports configuration + * of execution based on a statement name pattern and statement types. + */ +public abstract class DbClientServiceBase implements DbClientService { + private final Predicate predicate; + + /** + * Create a new instance based on the builder base each implementation must extend. + * + * @param builder builder to configure predicate to use + */ + protected DbClientServiceBase(DbClientServiceBuilderBase builder) { + this.predicate = builder.predicate(); + } + + @Override + public final Single statement(DbClientServiceContext context) { + if (predicate.test(context)) { + return apply(context); + } + return Single.just(context); + } + + /** + * This method is only invoked if the predicate for this service + * was passed. + * + * @param context db client invocation context + * @return single with the new context (or the same one if not modified) + * @see #statement(io.helidon.dbclient.DbClientServiceContext) + */ + protected abstract Single apply(DbClientServiceContext context); + + /** + * A base class for builders of {@link DbClientServiceBase}. + * + * @param type of the builder extending this class + */ + public abstract static class DbClientServiceBuilderBase> { + private static final Predicate YES = it -> true; + private static final Predicate NO = it -> false; + + @SuppressWarnings("unchecked") + private final B me = (B) this; + + // we can filter by statement name + private Set statementNames = new LinkedHashSet<>(); + // and statement type + private Set statementTypes = EnumSet.noneOf(DbStatementType.class); + private Predicate predicate; + private boolean enabled = true; + + /** + * No-op constructor. + */ + protected DbClientServiceBuilderBase() { + } + + /** + * Configure this client service from config. + *

+ * Supported keys: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
DB Client Service configuration options
keydefault valuedescription
statement-names An array of statement name patterns to apply this service for. If undefined, service + * would be executed for all statements. + * See {@link #statementNames(String...)} and {@link java.util.regex.Pattern}
statement-types An array of statement types to apply this service for. If undefined, service + * would be executed for all statements. + * See {@link #statementTypes(io.helidon.dbclient.DbStatementType...)}.
enabled{@code true}Whether this client service is enabled. See {@link #enabled(boolean)}
+ * + * @param config configuration on the node of this service + * @return updated builder instance + */ + public B config(Config config) { + config.get("statement-names").asList(String.class).ifPresent(this::statementNames); + config.get("statement-types").asList(cfg -> cfg.asString().map(DbStatementType::valueOf).get()) + .ifPresent(this::statementTypes); + config.get("enabled").asBoolean().ifPresent(this::enabled); + return me; + } + + /** + * Configure a predicate whose result will be used to decide whether to + * trigger this service or not. + *

+ * When a predicate is explicitly configured, {@link #statementNames(String...)} + * and {@link #statementTypes(io.helidon.dbclient.DbStatementType...)} is ignored. + * + * @param predicate predicate that should return {@code true} to enable this + * service, or {@code false} to disable it + * @return updated builder instance + */ + public B statementPredicate(Predicate predicate) { + this.predicate = predicate; + return me; + } + + /** + * Configure statement types this service will be triggered for. + * If an explicit {@link #statementPredicate(java.util.function.Predicate)} is configured, + * this method is ignored. + * + * @param types types that trigger this service + * @return updated builder instance + */ + public B statementTypes(DbStatementType... types) { + return statementTypes(List.of(types)); + } + + /** + * Configure statement name patterns this service will be triggered for. + * If an explicit {@link #statementPredicate(java.util.function.Predicate)} is configured, + * this method is ignored. + * + * @param names name patterns (as in {@link java.util.regex.Pattern}) that trigger this service + * @return updated builder instance + */ + public B statementNames(String... names) { + return statementNames(List.of(names)); + } + + /** + * Configure whether this service is enabled or not. + * + * @param enabled whether to enable this service or disable it, {@code true} by default + */ + public void enabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * Configures statement types from configuration. + * + * @param types types to add for this service + * @return updated builder instance + */ + protected B statementTypes(List types) { + this.statementTypes.addAll(types); + return me; + } + + /** + * Configures statement name patterns from configuration. + * + * @param names names to add for this service + * @return updated builder instance + */ + protected B statementNames(List names) { + this.statementNames.addAll(names); + return me; + } + + /** + * Set of statement name patterns. + * + * @return configured statement names + */ + protected Set statementNames() { + return statementNames; + } + + /** + * Set of statement types. + * + * @return configured statement types + */ + protected Set statementTypes() { + return statementTypes; + } + + /** + * Predicate used to build a client service. + *

+ * The predicate always returns {@code false} if service is disabled. + *

+ * The predicate is obtained from the configured predicate using + * {@link #statementPredicate(java.util.function.Predicate)}, + * if none is configured, it is created from configured statement types and statement names. + * If none are configured, the predicate just returns {@code true}. + * + * @return predicate to check whether this service should be invoked for current statement context + */ + protected Predicate predicate() { + if (!enabled) { + return NO; + } + + if (null != predicate) { + return predicate; + } + + List namePatterns = statementNames.stream() + .map(Pattern::compile) + .collect(Collectors.toList()); + + Set types = EnumSet.copyOf(statementTypes); + + Predicate namePredicate; + Predicate typePredicate; + if (namePatterns.isEmpty()) { + namePredicate = YES; + } else { + namePredicate = it -> { + String statementName = it.statementName(); + for (Pattern namePattern : namePatterns) { + if (namePattern.matcher(statementName).matches()) { + return true; + } + } + return false; + }; + } + if (types.isEmpty()) { + typePredicate = YES; + } else { + typePredicate = it -> types.contains(it.statementType()); + } + + return context -> namePredicate.test(context) && typePredicate.test(context); + } + } +} diff --git a/dbclient/common/src/main/java/io/helidon/dbclient/common/DbStatementContext.java b/dbclient/common/src/main/java/io/helidon/dbclient/common/DbStatementContext.java new file mode 100644 index 00000000000..1efbd3c030c --- /dev/null +++ b/dbclient/common/src/main/java/io/helidon/dbclient/common/DbStatementContext.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.dbclient.common; + +import io.helidon.dbclient.DbStatementType; + +/** + * Context of execution of a specific statement. + */ +public class DbStatementContext { + private final DbClientContext clientContext; + private final DbStatementType statementType; + private final String statementName; + private final String statementText; + + /** + * Create a new instance using a builder each implementation must extend. + * + * @param builder to get required fields from + */ + protected DbStatementContext(BuilderBase builder) { + this.clientContext = builder.clientContext; + this.statementType = builder.statementType; + this.statementName = builder.statementName; + this.statementText = builder.statementText; + } + + /** + * Create a new builder. + * + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Create a new instance of this class. + * + * @param clientContext DB client context + * @param statementType type of statement + * @param statementName name of statement + * @param statementText text of the statement to execute + * + * @return a new statement context + */ + public static DbStatementContext create(DbClientContext clientContext, + DbStatementType statementType, + String statementName, + String statementText) { + return builder() + .clientContext(clientContext) + .statementType(statementType) + .statementName(statementName) + .statementText(statementText) + .build(); + } + + /** + * Client context associated with the client executing this statement. + * @return client context + */ + public DbClientContext clientContext() { + return clientContext; + } + + /** + * Statement type of this statement. + * @return type of statement + */ + public DbStatementType statementType() { + return statementType; + } + + /** + * Name of this statement. + * @return name of statement + */ + public String statementName() { + return statementName; + } + + /** + * Statement text as configured. + * @return statement text + */ + public String statement() { + return statementText; + } + + /** + * A fluent API builder to create {@link io.helidon.dbclient.common.DbStatementContext}. + */ + public static final class Builder extends BuilderBase implements io.helidon.common.Builder { + private Builder() { + } + + @Override + public DbStatementContext build() { + return new DbStatementContext(this); + } + } + + /** + * A base builder that must be extended to implement a new {@link io.helidon.dbclient.common.DbStatementContext}. + * + * @param type of the builder extending this builder + */ + public abstract static class BuilderBase> { + @SuppressWarnings("unchecked") private final T me = (T) this; + private DbClientContext clientContext; + private DbStatementType statementType; + private String statementName; + private String statementText; + + /** + * A no-op constructor. + */ + protected BuilderBase() { + } + + /** + * Configure client context. + * + * @param clientContext client context + * @return updated builder instance + */ + public T clientContext(DbClientContext clientContext) { + this.clientContext = clientContext; + return me; + } + + /** + * Configure statement type. + * + * @param statementType the type of this statement + * @return updated builder instance + */ + public T statementType(DbStatementType statementType) { + this.statementType = statementType; + return me; + } + + /** + * Configure name of statement. + * + * @param statementName name of this statement + * @return updated builder instance + */ + public T statementName(String statementName) { + this.statementName = statementName; + return me; + } + + /** + * Configure text of statement. + * + * @param statementText content of this statement + * @return updated builder instance + */ + public T statementText(String statementText) { + this.statementText = statementText; + return me; + } + } +} diff --git a/dbclient/common/src/main/java/io/helidon/dbclient/common/InterceptorSupport.java b/dbclient/common/src/main/java/io/helidon/dbclient/common/InterceptorSupport.java deleted file mode 100644 index 2390ce7ec9e..00000000000 --- a/dbclient/common/src/main/java/io/helidon/dbclient/common/InterceptorSupport.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.dbclient.common; - -import java.util.Collections; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -import io.helidon.common.configurable.LruCache; -import io.helidon.dbclient.DbInterceptor; -import io.helidon.dbclient.DbStatementType; - -/** - * Support for interceptors. - */ -public interface InterceptorSupport { - - /** - * Get a list of interceptors to be executed for the specified statement. - * - * @param dbStatementType Type of the statement - * @param statementName Name of the statement (unnamed statements should have a name generated, see - * {@link AbstractDbExecute#generateName(io.helidon.dbclient.DbStatementType, String)} - * @return list of interceptors to executed for the defined type and name (may be empty) - * @see io.helidon.dbclient.DbInterceptor - */ - List interceptors(DbStatementType dbStatementType, String statementName); - - /** - * Create a new fluent API builder. - * - * @return a builder instance - */ - static Builder builder() { - return new Builder(); - } - - /** - * Fluent API builder for {@link io.helidon.dbclient.common.InterceptorSupport}. - */ - final class Builder implements io.helidon.common.Builder { - private final List interceptors = new LinkedList<>(); - private final Map> typeInterceptors = new EnumMap<>(DbStatementType.class); - private final Map> namedStatementInterceptors = new HashMap<>(); - - private Builder() { - } - - @Override - public InterceptorSupport build() { - // the result must be immutable (if somebody modifies the builder, the behavior must not change) - List interceptors = new LinkedList<>(this.interceptors); - final Map> typeInterceptors = new EnumMap<>(this.typeInterceptors); - final Map> namedStatementInterceptors = new HashMap<>(this.namedStatementInterceptors); - - final LruCache> cachedInterceptors = LruCache.create(); - return new InterceptorSupport() { - @Override - public List interceptors(DbStatementType dbStatementType, String statementName) { - // order is defined in DbInterceptor interface - return cachedInterceptors.computeValue(new CacheKey(dbStatementType, statementName), () -> { - List result = new LinkedList<>(); - addAll(result, namedStatementInterceptors.get(statementName)); - addAll(result, typeInterceptors.get(dbStatementType)); - result.addAll(interceptors); - return Optional.of(Collections.unmodifiableList(result)); - }).orElseGet(List::of); - } - - private void addAll(List result, List dbInterceptors) { - if (null == dbInterceptors) { - return; - } - result.addAll(dbInterceptors); - } - }; - } - - public Builder add(DbInterceptor interceptor) { - this.interceptors.add(interceptor); - return this; - } - - public Builder add(DbInterceptor interceptor, String... statementNames) { - for (String statementName : statementNames) { - this.namedStatementInterceptors.computeIfAbsent(statementName, theName -> new LinkedList<>()) - .add(interceptor); - } - return this; - } - - public Builder add(DbInterceptor interceptor, DbStatementType... dbStatementTypes) { - for (DbStatementType dbStatementType : dbStatementTypes) { - this.typeInterceptors.computeIfAbsent(dbStatementType, theType -> new LinkedList<>()) - .add(interceptor); - } - return this; - } - - private static final class CacheKey { - private final DbStatementType type; - private final String statementName; - - private CacheKey(DbStatementType type, String statementName) { - this.type = type; - this.statementName = statementName; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof CacheKey)) { - return false; - } - CacheKey cacheKey = (CacheKey) o; - return (type == cacheKey.type) - && statementName.equals(cacheKey.statementName); - } - - @Override - public int hashCode() { - return Objects.hash(type, statementName); - } - } - } - -} diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClient.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClient.java index 5761c520fed..9b9ab47b0c8 100644 --- a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClient.java +++ b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClient.java @@ -18,8 +18,8 @@ import java.util.Arrays; import java.util.List; import java.util.ServiceLoader; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; +import java.util.function.Supplier; import io.helidon.common.HelidonFeatures; import io.helidon.common.HelidonFlavor; @@ -28,10 +28,9 @@ import io.helidon.common.reactive.Subscribable; import io.helidon.common.serviceloader.HelidonServiceLoader; import io.helidon.config.Config; -import io.helidon.config.ConfigValue; import io.helidon.dbclient.spi.DbClientProvider; import io.helidon.dbclient.spi.DbClientProviderBuilder; -import io.helidon.dbclient.spi.DbInterceptorProvider; +import io.helidon.dbclient.spi.DbClientServiceProvider; import io.helidon.dbclient.spi.DbMapperProvider; /** @@ -155,14 +154,14 @@ final class Builder implements io.helidon.common.Builder { HelidonFeatures.register(HelidonFlavor.SE, "DbClient"); } - private final HelidonServiceLoader.Builder interceptorServices = HelidonServiceLoader.builder( - ServiceLoader.load(DbInterceptorProvider.class)); + private final HelidonServiceLoader.Builder clientServiceProviders = HelidonServiceLoader.builder( + ServiceLoader.load(DbClientServiceProvider.class)); /** * Provider specific database handler builder instance. */ private final DbClientProviderBuilder theBuilder; - private Config config; + private Config config = Config.empty(); /** * Create an instance of Helidon database handler builder. @@ -180,65 +179,18 @@ private Builder(DbClientProvider dbClientProvider) { */ @Override public DbClient build() { - // add interceptors from service loader - if (null != config) { - Config interceptors = config.get("interceptors"); - List providers = interceptorServices.build().asList(); - for (DbInterceptorProvider provider : providers) { - Config providerConfig = interceptors.get(provider.configKey()); - if (!providerConfig.exists()) { - continue; - } - // if configured, we want to at least add a global one - AtomicBoolean added = new AtomicBoolean(false); - Config global = providerConfig.get("global"); - if (global.exists() && !global.isLeaf()) { - // we must iterate through nodes - global.asNodeList().ifPresent(configs -> { - configs.forEach(globalConfig -> { - added.set(true); - addInterceptor(provider.create(globalConfig)); - }); - }); - } - - Config named = providerConfig.get("named"); - if (named.exists()) { - // we must iterate through nodes - named.asNodeList().ifPresent(configs -> { - configs.forEach(namedConfig -> { - ConfigValue> names = namedConfig.get("names").asList(String.class); - names.ifPresent(nameList -> { - added.set(true); - addInterceptor(provider.create(namedConfig), nameList.toArray(new String[0])); - }); - }); - }); - } - Config typed = providerConfig.get("typed"); - if (typed.exists()) { - typed.asNodeList().ifPresent(configs -> { - configs.forEach(typedConfig -> { - ConfigValue> types = typedConfig.get("types").asList(String.class); - types.ifPresent(typeList -> { - DbStatementType[] typeArray = typeList.stream() - .map(DbStatementType::valueOf) - .toArray(DbStatementType[]::new); - - added.set(true); - addInterceptor(provider.create(typedConfig), typeArray); - }); - }); - }); - } - if (!added.get()) { - if (global.exists()) { - addInterceptor(provider.create(global)); - } else { - addInterceptor(provider.create(providerConfig)); - } - } + // add client services from service loader + Config servicesConfig = config.get("services"); + List providers = clientServiceProviders.build().asList(); + for (DbClientServiceProvider provider : providers) { + Config providerConfig = servicesConfig.get(provider.configKey()); + if (!providerConfig.exists()) { + // this client service is on classpath, yet there is no configuration for it, so it is ignored + continue; } + + provider.create(providerConfig) + .forEach(this::addService); } return theBuilder.build(); @@ -251,44 +203,30 @@ public DbClient build() { * @param provider provider to add to the list of loaded providers * @return updated builder instance */ - public Builder addInterceptorProvider(DbInterceptorProvider provider) { - this.interceptorServices.addService(provider); - return this; - } - - /** - * Add a global interceptor. - * - * A global interceptor is applied to each statement. - * @param interceptor interceptor to apply - * @return updated builder instance - */ - public Builder addInterceptor(DbInterceptor interceptor) { - theBuilder.addInterceptor(interceptor); + public Builder addServiceProvider(DbClientServiceProvider provider) { + this.clientServiceProviders.addService(provider); return this; } /** - * Add an interceptor to specific named statements. + * Add a client service. * - * @param interceptor interceptor to apply - * @param statementNames names of statements to apply it on + * @param clientService clientService to apply * @return updated builder instance */ - public Builder addInterceptor(DbInterceptor interceptor, String... statementNames) { - theBuilder.addInterceptor(interceptor, statementNames); + public Builder addService(DbClientService clientService) { + theBuilder.addService(clientService); return this; } /** - * Add an interceptor to specific statement types. + * Add a client service. * - * @param interceptor interceptor to apply - * @param dbStatementTypes types of statements to apply it on + * @param clientServiceSupplier supplier of client service * @return updated builder instance */ - public Builder addInterceptor(DbInterceptor interceptor, DbStatementType... dbStatementTypes) { - theBuilder.addInterceptor(interceptor, dbStatementTypes); + public Builder addService(Supplier clientServiceSupplier) { + theBuilder.addService(clientServiceSupplier.get()); return this; } diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClientService.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClientService.java new file mode 100644 index 00000000000..af68f7a42f2 --- /dev/null +++ b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClientService.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019, 2020 Oracle and/or 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.dbclient; + +import io.helidon.common.reactive.Single; + +/** + * Services can modify the data used to execute a statement as well as + * react on a statement result. + *

+ * Example of such services: tracing, metrics. + *

+ * Order of execution of services is based on the order they are registered in a builder, or by their priority when + * loaded from a Java Service loader + */ +@FunctionalInterface +public interface DbClientService { + /** + * Statement execution to be intercepted. + * This method is called before the statement execution starts. + * If there is no need to modify the context and you do not block, + * return {@link Single#just(Object) Single.just(context)}. + * + * @param context Context to access data needed to process an interceptor + * @return single that completes when this service is finished + */ + Single statement(DbClientServiceContext context); +} diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbInterceptorContext.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClientServiceContext.java similarity index 86% rename from dbclient/dbclient/src/main/java/io/helidon/dbclient/DbInterceptorContext.java rename to dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClientServiceContext.java index 1b4491f873b..7ef0529af82 100644 --- a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbInterceptorContext.java +++ b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClientServiceContext.java @@ -25,18 +25,18 @@ /** * Interceptor context to get (and possibly manipulate) database operations. *

- * This is a mutable object - acts as a builder during the invocation of {@link io.helidon.dbclient.DbInterceptor}. + * This is a mutable object - acts as a builder during the invocation of {@link DbClientService}. * The interceptors are executed sequentially, so there is no need for synchronization. */ -public interface DbInterceptorContext { +public interface DbClientServiceContext { /** * Create a new interceptor context for a database provider. * * @param dbType a short name of the db type (such as jdbc:mysql) * @return a new interceptor context ready to be configured */ - static DbInterceptorContext create(String dbType) { - return new DbInterceptorContextImpl(dbType); + static DbClientServiceContext create(String dbType) { + return new DbClientServiceContextImpl(dbType); } /** @@ -123,7 +123,7 @@ static DbInterceptorContext create(String dbType) { * @param context context to use * @return updated interceptor context */ - DbInterceptorContext context(Context context); + DbClientServiceContext context(Context context); /** * Set a new statement name to be used. @@ -131,7 +131,7 @@ static DbInterceptorContext create(String dbType) { * @param newName statement name to use * @return updated interceptor context */ - DbInterceptorContext statementName(String newName); + DbClientServiceContext statementName(String newName); /** * Set a new future to mark completion of the statement. @@ -139,7 +139,7 @@ static DbInterceptorContext create(String dbType) { * @param statementFuture future * @return updated interceptor context */ - DbInterceptorContext statementFuture(CompletionStage statementFuture); + DbClientServiceContext statementFuture(CompletionStage statementFuture); /** * Set a new future to mark completion of the result (e.g. query or number of modified records). @@ -147,7 +147,7 @@ static DbInterceptorContext create(String dbType) { * @param queryFuture future * @return updated interceptor context */ - DbInterceptorContext resultFuture(CompletionStage queryFuture); + DbClientServiceContext resultFuture(CompletionStage queryFuture); /** * Set a new statement with indexed parameters to be used. @@ -156,7 +156,7 @@ static DbInterceptorContext create(String dbType) { * @param indexedParams indexed parameters * @return updated interceptor context */ - DbInterceptorContext statement(String statement, List indexedParams); + DbClientServiceContext statement(String statement, List indexedParams); /** * Set a new statement with named parameters to be used. @@ -165,7 +165,7 @@ static DbInterceptorContext create(String dbType) { * @param namedParams named parameters * @return updated interceptor context */ - DbInterceptorContext statement(String statement, Map namedParams); + DbClientServiceContext statement(String statement, Map namedParams); /** * Set new indexed parameters to be used. @@ -174,7 +174,7 @@ static DbInterceptorContext create(String dbType) { * @return updated interceptor context * @throws IllegalArgumentException in case the statement is using named parameters */ - DbInterceptorContext parameters(List indexedParameters); + DbClientServiceContext parameters(List indexedParameters); /** * Set new named parameters to be used. @@ -183,7 +183,7 @@ static DbInterceptorContext create(String dbType) { * @return updated interceptor context * @throws IllegalArgumentException in case the statement is using indexed parameters */ - DbInterceptorContext parameters(Map namedParameters); + DbClientServiceContext parameters(Map namedParameters); /** * Set the type of the statement. @@ -191,5 +191,5 @@ static DbInterceptorContext create(String dbType) { * @param type statement type * @return updated interceptor context */ - DbInterceptorContext statementType(DbStatementType type); + DbClientServiceContext statementType(DbStatementType type); } diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbInterceptorContextImpl.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClientServiceContextImpl.java similarity index 80% rename from dbclient/dbclient/src/main/java/io/helidon/dbclient/DbInterceptorContextImpl.java rename to dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClientServiceContextImpl.java index dabe5d9ffcf..e1b25759c27 100644 --- a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbInterceptorContextImpl.java +++ b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClientServiceContextImpl.java @@ -23,9 +23,9 @@ import io.helidon.common.context.Context; /** - * Interceptor is a mutable object that is sent to {@link io.helidon.dbclient.DbInterceptor}. + * Client service context is a mutable object that is sent to {@link DbClientService}. */ -class DbInterceptorContextImpl implements DbInterceptorContext { +class DbClientServiceContextImpl implements DbClientServiceContext { private final String dbType; private DbStatementType dbStatementType; private Context context; @@ -37,7 +37,7 @@ class DbInterceptorContextImpl implements DbInterceptorContext { private Map namedParams; private boolean indexed; - DbInterceptorContextImpl(String dbType) { + DbClientServiceContextImpl(String dbType) { this.dbType = dbType; } @@ -93,19 +93,19 @@ public boolean isNamed() { } @Override - public DbInterceptorContext context(Context context) { + public DbClientServiceContext context(Context context) { this.context = context; return this; } @Override - public DbInterceptorContext statementName(String newName) { + public DbClientServiceContext statementName(String newName) { this.statementName = newName; return this; } @Override - public DbInterceptorContext statementFuture(CompletionStage statementFuture) { + public DbClientServiceContext statementFuture(CompletionStage statementFuture) { this.statementFuture = statementFuture; return this; } @@ -116,13 +116,13 @@ public CompletionStage resultFuture() { } @Override - public DbInterceptorContext resultFuture(CompletionStage resultFuture) { + public DbClientServiceContext resultFuture(CompletionStage resultFuture) { this.queryFuture = resultFuture; return this; } @Override - public DbInterceptorContext statement(String statement, List indexedParams) { + public DbClientServiceContext statement(String statement, List indexedParams) { this.statement = statement; this.indexedParams = indexedParams; this.indexed = true; @@ -130,7 +130,7 @@ public DbInterceptorContext statement(String statement, List indexedPara } @Override - public DbInterceptorContext statement(String statement, Map namedParams) { + public DbClientServiceContext statement(String statement, Map namedParams) { this.statement = statement; this.namedParams = namedParams; this.indexed = false; @@ -138,7 +138,7 @@ public DbInterceptorContext statement(String statement, Map name } @Override - public DbInterceptorContext parameters(List indexedParameters) { + public DbClientServiceContext parameters(List indexedParameters) { if (indexed) { this.indexedParams = indexedParameters; } else { @@ -149,7 +149,7 @@ public DbInterceptorContext parameters(List indexedParameters) { } @Override - public DbInterceptorContext parameters(Map namedParameters) { + public DbClientServiceContext parameters(Map namedParameters) { if (indexed) { throw new IllegalStateException("Cannot configure named parameters for a statement that expects indexed " + "parameters"); @@ -165,7 +165,7 @@ public DbStatementType statementType() { } @Override - public DbInterceptorContext statementType(DbStatementType type) { + public DbClientServiceContext statementType(DbStatementType type) { this.dbStatementType = type; return this; } diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbInterceptor.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbInterceptor.java deleted file mode 100644 index 94eddf13d17..00000000000 --- a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbInterceptor.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.dbclient; - -import java.util.concurrent.CompletionStage; - -/** - * Interceptor to handle work around a database statement. - * Example of such interceptors: tracing, metrics. - *

- * Interceptors can be defined as global interceptors, interceptors for a type of a statement and interceptors for a named - * statement. - * These are executed in the following order: - *

    - *
  1. Named interceptors - if there are any interceptors configured for a specific statement, they are executed first
  2. - *
  3. Type interceptors - if there are any interceptors configured for a type of statement, they are executed next
  4. - *
  5. Global interceptors - if there are any interceptors configured globally, they are executed last
  6. - *
- * Order of interceptors within a group is based on the order they are registered in a builder, or by their priority when - * loaded from a Java Service loader - */ -@FunctionalInterface -public interface DbInterceptor { - /** - * Statement execution to be intercepted. - * This method is called before the statement execution starts. - * - * @param context Context to access data needed to process an interceptor - * @return completion stage that completes when this interceptor is finished - */ - CompletionStage statement(DbInterceptorContext context); -} diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/DbClientProviderBuilder.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/DbClientProviderBuilder.java index f64771516e9..f42ddefecef 100644 --- a/dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/DbClientProviderBuilder.java +++ b/dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/DbClientProviderBuilder.java @@ -20,9 +20,8 @@ import io.helidon.common.mapper.MapperManager; import io.helidon.config.Config; import io.helidon.dbclient.DbClient; -import io.helidon.dbclient.DbInterceptor; +import io.helidon.dbclient.DbClientService; import io.helidon.dbclient.DbMapper; -import io.helidon.dbclient.DbStatementType; import io.helidon.dbclient.DbStatements; /** @@ -114,37 +113,10 @@ public interface DbClientProviderBuilder> e * This allows to add implementation of tracing, metrics, logging etc. without the need to hard-code these into * the base. * - * @param interceptor interceptor instance + * @param clientService interceptor instance * @return updated builder instance */ - T addInterceptor(DbInterceptor interceptor); - - /** - * Add an interceptor that is active only on the configured statement names. - * This interceptor is only executed on named statements. - * - * @param interceptor interceptor instance - * @param statementNames statement names to be active on - * @return updated builder instance - */ - T addInterceptor(DbInterceptor interceptor, String... statementNames); - - /** - * Add an interceptor thas is active only on configured statement types. - * This interceptor is executed on all statements of that type. - *

- * Note the specific handling of the following types: - *

    - *
  • {@link io.helidon.dbclient.DbStatementType#DML} - used only when the statement is created as a DML statement - * such as when using {@link io.helidon.dbclient.DbExecute#createDmlStatement(String)} - * (this interceptor would not be enabled for inserts, updates, deletes)
  • - *
- * - * @param interceptor interceptor instance - * @param dbStatementTypes statement types to be active on - * @return updated builder instance - */ - T addInterceptor(DbInterceptor interceptor, DbStatementType... dbStatementTypes); + T addService(DbClientService clientService); /** * Build database handler for specific provider. diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/DbInterceptorProvider.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/DbClientServiceProvider.java similarity index 88% rename from dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/DbInterceptorProvider.java rename to dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/DbClientServiceProvider.java index 1e7f7c6f9ca..6d0d0ef4014 100644 --- a/dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/DbInterceptorProvider.java +++ b/dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/DbClientServiceProvider.java @@ -15,13 +15,15 @@ */ package io.helidon.dbclient.spi; +import java.util.Collection; + import io.helidon.config.Config; -import io.helidon.dbclient.DbInterceptor; +import io.helidon.dbclient.DbClientService; /** - * Java service loader service to configure interceptors. + * Java service loader service to configure client services. */ -public interface DbInterceptorProvider { +public interface DbClientServiceProvider { /** * The configuration key expected in config. @@ -45,6 +47,6 @@ public interface DbInterceptorProvider { * @param config configuration node with additional properties that are (maybe) configured for this interceptor * @return an interceptor to handle DB statements */ - DbInterceptor create(Config config); + Collection create(Config config); } diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/package-info.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/package-info.java index 8130a659a4a..46ed963f576 100644 --- a/dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/package-info.java +++ b/dbclient/dbclient/src/main/java/io/helidon/dbclient/spi/package-info.java @@ -17,7 +17,7 @@ * Service provider interface for Helidon DB. * The main entry point for driver implementor is {@link io.helidon.dbclient.spi.DbClientProvider}. * - * @see io.helidon.dbclient.spi.DbInterceptorProvider + * @see io.helidon.dbclient.spi.DbClientServiceProvider * @see io.helidon.dbclient.spi.DbMapperProvider */ package io.helidon.dbclient.spi; diff --git a/dbclient/dbclient/src/main/java/module-info.java b/dbclient/dbclient/src/main/java/module-info.java index 91e641161b1..a4e0b346455 100644 --- a/dbclient/dbclient/src/main/java/module-info.java +++ b/dbclient/dbclient/src/main/java/module-info.java @@ -32,5 +32,5 @@ uses io.helidon.dbclient.spi.DbClientProvider; uses io.helidon.dbclient.spi.DbMapperProvider; - uses io.helidon.dbclient.spi.DbInterceptorProvider; + uses io.helidon.dbclient.spi.DbClientServiceProvider; } diff --git a/dbclient/health/src/main/java/io/helidon/dbclient/health/DbClientHealthCheck.java b/dbclient/health/src/main/java/io/helidon/dbclient/health/DbClientHealthCheck.java index 8abe4c69ff9..538d4015729 100644 --- a/dbclient/health/src/main/java/io/helidon/dbclient/health/DbClientHealthCheck.java +++ b/dbclient/health/src/main/java/io/helidon/dbclient/health/DbClientHealthCheck.java @@ -46,7 +46,8 @@ private DbClientHealthCheck(Builder builder) { * * @param dbClient A database that implements {@link io.helidon.dbclient.DbClient#ping()} * @return health check that can be used with - * {@link io.helidon.health.HealthSupport.Builder#add(org.eclipse.microprofile.health.HealthCheck...)} + * {@link io.helidon.health.HealthSupport.Builder#addReadiness(org.eclipse.microprofile.health.HealthCheck...)} + * or {@link io.helidon.health.HealthSupport.Builder#addLiveness(org.eclipse.microprofile.health.HealthCheck...)} */ public static DbClientHealthCheck create(DbClient dbClient) { return builder(dbClient).build(); diff --git a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcDbClient.java b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcDbClient.java index 0a4d7906331..0511b8865a3 100644 --- a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcDbClient.java +++ b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcDbClient.java @@ -17,6 +17,7 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutorService; @@ -33,6 +34,7 @@ import io.helidon.common.reactive.Subscribable; import io.helidon.dbclient.DbClient; import io.helidon.dbclient.DbClientException; +import io.helidon.dbclient.DbClientService; import io.helidon.dbclient.DbExecute; import io.helidon.dbclient.DbMapperManager; import io.helidon.dbclient.DbStatementDml; @@ -42,7 +44,7 @@ import io.helidon.dbclient.DbStatements; import io.helidon.dbclient.DbTransaction; import io.helidon.dbclient.common.AbstractDbExecute; -import io.helidon.dbclient.common.InterceptorSupport; +import io.helidon.dbclient.common.DbStatementContext; /** * Helidon DB implementation for JDBC drivers. @@ -60,7 +62,7 @@ class JdbcDbClient implements DbClient { private final DbStatements statements; private final DbMapperManager dbMapperManager; private final MapperManager mapperManager; - private final InterceptorSupport interceptors; + private final List clientServices; JdbcDbClient(JdbcDbClientProviderBuilder builder) { this.executorService = builder.executorService(); @@ -68,7 +70,7 @@ class JdbcDbClient implements DbClient { this.statements = builder.statements(); this.dbMapperManager = builder.dbMapperManager(); this.mapperManager = builder.mapperManager(); - this.interceptors = builder.interceptors(); + this.clientServices = builder.clientServices(); } @Override @@ -78,7 +80,7 @@ public > T inTransaction(Function JdbcTxExecute execute = new JdbcTxExecute( statements, executorService, - interceptors, + clientServices, connectionPool, dbMapperManager, mapperManager); @@ -106,7 +108,7 @@ public > T inTransaction(Function - so here we resume with the commit future that provides us with either empty multi, or error multi - the flatMap just returns the multi result of the completion stage */ - multi = multi.onCompleteResumeWith(Single.from(commitFuture).flatMap(Function.identity())); + multi = multi.onCompleteResumeWith(Single.create(commitFuture).flatMap(Function.identity())); // if result completes with an exception, or commit failed, we attempt a rollback multi = multi.onError(throwable -> { @@ -122,7 +124,7 @@ public > T inTransaction(Function future = future.exceptionally(RollbackHandler.create(execute, Level.WARNING)); - return (T) Single.from(future); + return (T) Single.create(future); } else { execute.doRollback(); throw new IllegalStateException("You must return a Single or Multi instance to inTransaction, yet " @@ -170,11 +172,12 @@ public T apply(Throwable t) { public > T execute(Function executor) { JdbcExecute execute = new JdbcExecute(statements, - executorService, - interceptors, - connectionPool, - dbMapperManager, - mapperManager); + JdbcExecute.createContext(statements, + executorService, + clientServices, + connectionPool, + dbMapperManager, + mapperManager)); Subscribable result; @@ -226,19 +229,24 @@ private static final class JdbcTxExecute extends JdbcExecute implements DbTransa private JdbcTxExecute(DbStatements statements, ExecutorService executorService, - InterceptorSupport interceptors, + List clientServices, ConnectionPool connectionPool, DbMapperManager dbMapperManager, MapperManager mapperManager) { - super(statements, createTxContext(executorService, interceptors, connectionPool, dbMapperManager, mapperManager)); + super(statements, JdbcExecuteContext.jdbcBuilder() + .statements(statements) + .clientServices(clientServices) + .dbType(connectionPool.dbType()) + .connection(createConnection(executorService, connectionPool)) + .dbMapperManager(dbMapperManager) + .mapperManager(mapperManager) + .executorService(executorService) + .build()); } - private static JdbcExecuteContext createTxContext(ExecutorService executorService, - InterceptorSupport interceptors, - ConnectionPool connectionPool, - DbMapperManager dbMapperManager, - MapperManager mapperManager) { - CompletionStage connection = CompletableFuture.supplyAsync(connectionPool::connection, executorService) + private static CompletionStage createConnection(ExecutorService executorService, + ConnectionPool connectionPool) { + return CompletableFuture.supplyAsync(connectionPool::connection, executorService) .thenApply(conn -> { try { conn.setAutoCommit(false); @@ -247,13 +255,6 @@ private static JdbcExecuteContext createTxContext(ExecutorService executorServic } return conn; }); - - return JdbcExecuteContext.create(executorService, - interceptors, - connectionPool.dbType(), - connection, - dbMapperManager, - mapperManager); } @Override @@ -302,17 +303,9 @@ private JdbcExecute(DbStatements statements, JdbcExecuteContext context) { this.context = context; } - private JdbcExecute(DbStatements statements, - ExecutorService executorService, - InterceptorSupport interceptors, - ConnectionPool connectionPool, - DbMapperManager dbMapperManager, - MapperManager mapperManager) { - this(statements, createContext(executorService, interceptors, connectionPool, dbMapperManager, mapperManager)); - } - - private static JdbcExecuteContext createContext(ExecutorService executorService, - InterceptorSupport interceptors, + private static JdbcExecuteContext createContext(DbStatements statements, + ExecutorService executorService, + List clientServices, ConnectionPool connectionPool, DbMapperManager dbMapperManager, MapperManager mapperManager) { @@ -326,49 +319,52 @@ private static JdbcExecuteContext createContext(ExecutorService executorService, return conn; }); - return JdbcExecuteContext.create(executorService, - interceptors, - connectionPool.dbType(), - connection, - dbMapperManager, - mapperManager); + return JdbcExecuteContext.jdbcBuilder() + .statements(statements) + .executorService(executorService) + .connection(connection) + .clientServices(clientServices) + .dbMapperManager(dbMapperManager) + .mapperManager(mapperManager) + .dbType(connectionPool.dbType()) + .build(); } @Override public DbStatementQuery createNamedQuery(String statementName, String statement) { return new JdbcStatementQuery(context, - JdbcStatementContext.create(DbStatementType.QUERY, statementName, statement)); + DbStatementContext.create(context, DbStatementType.QUERY, statementName, statement)); } @Override public DbStatementGet createNamedGet(String statementName, String statement) { return new JdbcStatementGet(context, - JdbcStatementContext.create(DbStatementType.GET, statementName, statement)); + DbStatementContext.create(context, DbStatementType.GET, statementName, statement)); } @Override public DbStatementDml createNamedDmlStatement(String statementName, String statement) { return new JdbcStatementDml(context, - JdbcStatementContext.create(DbStatementType.DML, statementName, statement)); + DbStatementContext.create(context, DbStatementType.DML, statementName, statement)); } @Override public DbStatementDml createNamedInsert(String statementName, String statement) { return new JdbcStatementDml(context, - JdbcStatementContext.create(DbStatementType.INSERT, statementName, statement)); + DbStatementContext.create(context, DbStatementType.INSERT, statementName, statement)); } @Override public DbStatementDml createNamedUpdate(String statementName, String statement) { return new JdbcStatementDml(context, - JdbcStatementContext.create(DbStatementType.UPDATE, statementName, statement)); + DbStatementContext.create(context, DbStatementType.UPDATE, statementName, statement)); } @Override public DbStatementDml createNamedDelete(String statementName, String statement) { return new JdbcStatementDml(context, - JdbcStatementContext.create(DbStatementType.DELETE, statementName, statement)); + DbStatementContext.create(context, DbStatementType.DELETE, statementName, statement)); } JdbcExecuteContext context() { diff --git a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcDbClientProviderBuilder.java b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcDbClientProviderBuilder.java index 473a27e141c..737e9f64f36 100644 --- a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcDbClientProviderBuilder.java +++ b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcDbClientProviderBuilder.java @@ -15,6 +15,8 @@ */ package io.helidon.dbclient.jdbc; +import java.util.LinkedList; +import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.function.Supplier; @@ -25,12 +27,10 @@ import io.helidon.config.Config; import io.helidon.dbclient.DbClient; import io.helidon.dbclient.DbClientException; -import io.helidon.dbclient.DbInterceptor; +import io.helidon.dbclient.DbClientService; import io.helidon.dbclient.DbMapper; import io.helidon.dbclient.DbMapperManager; -import io.helidon.dbclient.DbStatementType; import io.helidon.dbclient.DbStatements; -import io.helidon.dbclient.common.InterceptorSupport; import io.helidon.dbclient.spi.DbClientProviderBuilder; import io.helidon.dbclient.spi.DbMapperProvider; @@ -39,10 +39,10 @@ * the {@link io.helidon.dbclient.spi.DbClientProviderBuilder} from Helidon DB API. */ public final class JdbcDbClientProviderBuilder implements DbClientProviderBuilder { - - private final InterceptorSupport.Builder interceptors = InterceptorSupport.builder(); private final DbMapperManager.Builder dbMapperBuilder = DbMapperManager.builder(); + private final List clientServices = new LinkedList<>(); + private String url; private String username; private String password; @@ -196,20 +196,8 @@ public JdbcDbClientProviderBuilder addMapperProvider(DbMapperProvider provider) } @Override - public JdbcDbClientProviderBuilder addInterceptor(DbInterceptor interceptor) { - this.interceptors.add(interceptor); - return this; - } - - @Override - public JdbcDbClientProviderBuilder addInterceptor(DbInterceptor interceptor, String... statementNames) { - this.interceptors.add(interceptor, statementNames); - return this; - } - - @Override - public JdbcDbClientProviderBuilder addInterceptor(DbInterceptor interceptor, DbStatementType... dbStatementTypes) { - this.interceptors.add(interceptor, dbStatementTypes); + public JdbcDbClientProviderBuilder addService(DbClientService clientService) { + this.clientServices.add(clientService); return this; } @@ -217,8 +205,8 @@ DbStatements statements() { return statements; } - InterceptorSupport interceptors() { - return interceptors.build(); + List clientServices() { + return List.copyOf(clientServices); } DbMapperManager dbMapperManager() { diff --git a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcExecuteContext.java b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcExecuteContext.java index 75066fa583e..6e299c0ef02 100644 --- a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcExecuteContext.java +++ b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcExecuteContext.java @@ -21,67 +21,37 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; -import io.helidon.common.mapper.MapperManager; -import io.helidon.dbclient.DbMapperManager; -import io.helidon.dbclient.common.InterceptorSupport; +import io.helidon.dbclient.common.DbClientContext; /** * Stuff needed by each and every statement. */ -final class JdbcExecuteContext { +final class JdbcExecuteContext extends DbClientContext { + private final ConcurrentHashMap.KeySetView, Boolean> futures = ConcurrentHashMap.newKeySet(); private final ExecutorService executorService; - private final InterceptorSupport interceptors; - private final DbMapperManager dbMapperManager; - private final MapperManager mapperManager; private final String dbType; private final CompletionStage connection; - private final ConcurrentHashMap.KeySetView, Boolean> futures = ConcurrentHashMap.newKeySet(); - private JdbcExecuteContext(ExecutorService executorService, - InterceptorSupport interceptors, - DbMapperManager dbMapperManager, - MapperManager mapperManager, - String dbType, - CompletionStage connection) { - this.executorService = executorService; - this.interceptors = interceptors; - this.dbMapperManager = dbMapperManager; - this.mapperManager = mapperManager; - this.dbType = dbType; - this.connection = connection; + private JdbcExecuteContext(Builder builder) { + super(builder); + this.executorService = builder.executorService; + this.dbType = builder.dbType; + this.connection = builder.connection; } - static JdbcExecuteContext create(ExecutorService executorService, - InterceptorSupport interceptors, - String dbType, - CompletionStage connection, - DbMapperManager dbMapperManager, - MapperManager mapperManager) { - return new JdbcExecuteContext(executorService, - interceptors, - dbMapperManager, - mapperManager, - dbType, - connection); + /** + * Builder to create new instances. + * @return a new builder instance + */ + static Builder jdbcBuilder() { + return new Builder(); } ExecutorService executorService() { return executorService; } - InterceptorSupport interceptors() { - return interceptors; - } - - DbMapperManager dbMapperManager() { - return dbMapperManager; - } - - MapperManager mapperManager() { - return mapperManager; - } - String dbType() { return dbType; } @@ -94,7 +64,7 @@ void addFuture(CompletableFuture queryFuture) { this.futures.add(queryFuture); } - public CompletionStage whenComplete() { + CompletionStage whenComplete() { CompletionStage overallStage = CompletableFuture.completedFuture(null); for (CompletableFuture future : futures) { @@ -105,4 +75,29 @@ public CompletionStage whenComplete() { }); } + static class Builder extends BuilderBase implements io.helidon.common.Builder { + private ExecutorService executorService; + private String dbType; + private CompletionStage connection; + + @Override + public JdbcExecuteContext build() { + return new JdbcExecuteContext(this); + } + + Builder executorService(ExecutorService executorService) { + this.executorService = executorService; + return this; + } + + Builder dbType(String dbType) { + this.dbType = dbType; + return this; + } + + Builder connection(CompletionStage connection) { + this.connection = connection; + return this; + } + } } diff --git a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatement.java b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatement.java index 1285e243150..0d0f3f11629 100644 --- a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatement.java +++ b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,9 +30,10 @@ import java.util.logging.Logger; import io.helidon.dbclient.DbClientException; -import io.helidon.dbclient.DbInterceptorContext; +import io.helidon.dbclient.DbClientServiceContext; import io.helidon.dbclient.DbStatement; import io.helidon.dbclient.common.AbstractStatement; +import io.helidon.dbclient.common.DbStatementContext; /** * Common JDBC statement builder. @@ -50,13 +51,8 @@ abstract class JdbcStatement, R> extends AbstractSta private final CompletionStage connection; private final JdbcExecuteContext executeContext; - JdbcStatement(JdbcExecuteContext executeContext, JdbcStatementContext statementContext) { - super(statementContext.statementType(), - statementContext.statementName(), - statementContext.statement(), - executeContext.dbMapperManager(), - executeContext.mapperManager(), - executeContext.interceptors()); + JdbcStatement(JdbcExecuteContext executeContext, DbStatementContext statementContext) { + super(statementContext); this.executeContext = executeContext; this.dbType = executeContext.dbType(); @@ -64,7 +60,7 @@ abstract class JdbcStatement, R> extends AbstractSta this.executorService = executeContext.executorService(); } - PreparedStatement build(Connection conn, DbInterceptorContext dbContext) { + PreparedStatement build(Connection conn, DbClientServiceContext dbContext) { LOGGER.fine(() -> String.format("Building SQL statement: %s", dbContext.statement())); String statement = dbContext.statement(); String statementName = dbContext.statementName(); @@ -82,28 +78,6 @@ PreparedStatement build(Connection conn, DbInterceptorContext dbContext) { } } - /** - * Switch to {@link #build(java.sql.Connection, io.helidon.dbclient.DbInterceptorContext)} and use interceptors. - * - * @param connection connection to use - * @return prepared statement - */ - @Deprecated - protected PreparedStatement build(Connection connection) { - LOGGER.fine(() -> String.format("Building SQL statement: %s", statement())); - switch (paramType()) { - // Statement may not contain any parameters, no conversion is needed. - case UNKNOWN: - return prepareStatement(connection, statementName(), statement()); - case INDEXED: - return prepareIndexedStatement(connection, statementName(), statement(), indexedParams()); - case NAMED: - return prepareNamedStatement(connection, statementName(), statement(), namedParams()); - default: - throw new IllegalStateException("Unknown SQL statement type"); - } - } - @Override protected String dbType() { return dbType; diff --git a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementContext.java b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementContext.java deleted file mode 100644 index 30681ccfc81..00000000000 --- a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementContext.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.dbclient.jdbc; - -import io.helidon.dbclient.DbStatementType; - -/** - * Stuff needed by each and every statement. - */ -class JdbcStatementContext { - - private final DbStatementType statementType; - private final String statementName; - private final String statement; - - private JdbcStatementContext(DbStatementType statementType, String statementName, String statement) { - this.statementType = statementType; - this.statementName = statementName; - this.statement = statement; - } - - static JdbcStatementContext create(DbStatementType statementType, String statementName, String statement) { - return new JdbcStatementContext(statementType, statementName, statement); - } - - DbStatementType statementType() { - return statementType; - } - - String statementName() { - return statementName; - } - - String statement() { - return statement; - } - -} diff --git a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementDml.java b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementDml.java index acd5d685e3f..9aba47a9f10 100644 --- a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementDml.java +++ b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementDml.java @@ -18,21 +18,21 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import io.helidon.common.reactive.Single; -import io.helidon.dbclient.DbInterceptorContext; +import io.helidon.dbclient.DbClientServiceContext; import io.helidon.dbclient.DbStatementDml; +import io.helidon.dbclient.common.DbStatementContext; class JdbcStatementDml extends JdbcStatement> implements DbStatementDml { JdbcStatementDml(JdbcExecuteContext executeContext, - JdbcStatementContext statementContext) { + DbStatementContext statementContext) { super(executeContext, statementContext); } @Override - protected Single doExecute(CompletionStage dbContextFuture, + protected Single doExecute(Single dbContextFuture, CompletableFuture statementFuture, CompletableFuture queryFuture) { @@ -45,19 +45,19 @@ protected Single doExecute(CompletionStage dbContext return null; }); - return Single.from(dbContextFuture) + return dbContextFuture .flatMapSingle(dbContext -> doExecute(dbContext, statementFuture, queryFuture)); } - private Single doExecute(DbInterceptorContext dbContext, + private Single doExecute(DbClientServiceContext dbContext, CompletableFuture statementFuture, CompletableFuture queryFuture) { - return Single.from(connection()) + return Single.create(connection()) .flatMapSingle(connection -> doExecute(dbContext, connection, statementFuture, queryFuture)); } - private Single doExecute(DbInterceptorContext dbContext, + private Single doExecute(DbClientServiceContext dbContext, Connection connection, CompletableFuture statementFuture, CompletableFuture queryFuture) { @@ -65,10 +65,10 @@ private Single doExecute(DbInterceptorContext dbContext, executorService().submit(() -> callStatement(dbContext, connection, statementFuture, queryFuture)); // the query future is reused, as it completes with the number of updated records - return Single.from(queryFuture); + return Single.create(queryFuture); } - private void callStatement(DbInterceptorContext dbContext, + private void callStatement(DbClientServiceContext dbContext, Connection connection, CompletableFuture statementFuture, CompletableFuture queryFuture) { diff --git a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementGet.java b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementGet.java index 1ee499eaa5b..5207d724f33 100644 --- a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementGet.java +++ b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementGet.java @@ -22,6 +22,7 @@ import io.helidon.common.reactive.Single; import io.helidon.dbclient.DbRow; import io.helidon.dbclient.DbStatementGet; +import io.helidon.dbclient.common.DbStatementContext; /** * A JDBC get implementation. @@ -33,7 +34,7 @@ class JdbcStatementGet implements DbStatementGet { private final JdbcStatementQuery query; JdbcStatementGet(JdbcExecuteContext executeContext, - JdbcStatementContext statementContext) { + DbStatementContext statementContext) { this.query = new JdbcStatementQuery(executeContext, statementContext); @@ -77,7 +78,7 @@ public JdbcStatementGet addParam(String name, Object parameter) { @Override public Single> execute() { - return Single.from(query.execute()) + return Single.create(query.execute()) .toOptionalSingle(); } } diff --git a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementQuery.java b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementQuery.java index a92aa03b6e1..47b5a847fce 100644 --- a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementQuery.java +++ b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementQuery.java @@ -25,7 +25,6 @@ import java.util.Map; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutorService; import java.util.concurrent.Flow; import java.util.concurrent.LinkedBlockingQueue; @@ -42,11 +41,12 @@ import io.helidon.common.mapper.MapperManager; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; +import io.helidon.dbclient.DbClientServiceContext; import io.helidon.dbclient.DbColumn; -import io.helidon.dbclient.DbInterceptorContext; import io.helidon.dbclient.DbMapperManager; import io.helidon.dbclient.DbRow; import io.helidon.dbclient.DbStatementQuery; +import io.helidon.dbclient.common.DbStatementContext; /** * Implementation of query. @@ -57,30 +57,30 @@ class JdbcStatementQuery extends JdbcStatement> i private static final Logger LOGGER = Logger.getLogger(JdbcStatementQuery.class.getName()); JdbcStatementQuery(JdbcExecuteContext executeContext, - JdbcStatementContext statementContext) { + DbStatementContext statementContext) { super(executeContext, statementContext); } @Override - protected Multi doExecute(CompletionStage dbContextFuture, + protected Multi doExecute(Single dbContextFuture, CompletableFuture statementFuture, CompletableFuture queryFuture) { executeContext().addFuture(queryFuture); - return Single.from(dbContextFuture) + return dbContextFuture .flatMap(dbContext -> doExecute(dbContext, statementFuture, queryFuture)); } - private Multi doExecute(DbInterceptorContext dbContext, + private Multi doExecute(DbClientServiceContext dbContext, CompletableFuture statementFuture, CompletableFuture queryFuture) { - return Single.from(connection()) + return Single.create(connection()) .flatMap(connection -> doExecute(dbContext, connection, statementFuture, queryFuture)); } - private Multi doExecute(DbInterceptorContext dbContext, + private Multi doExecute(DbClientServiceContext dbContext, Connection connection, CompletableFuture statementFuture, CompletableFuture queryFuture) { @@ -118,7 +118,7 @@ private Multi doExecute(DbInterceptorContext dbContext, } }); - return Single.from(result).flatMap(Function.identity()); + return Single.create(result).flatMap(Function.identity()); } @@ -129,7 +129,7 @@ static Multi processResultSet( CompletableFuture queryFuture, ResultSet resultSet) { - return Multi.from(new JdbcDbRows(resultSet, + return Multi.create(new JdbcDbRows(resultSet, executorService, dbMapperManager, mapperManager, @@ -189,10 +189,6 @@ private static Class classByName(String columnClassName) { } } - String name() { - return statementName(); - } - private static final class JdbcDbRows { private final AtomicBoolean resultRequested = new AtomicBoolean(); private final ExecutorService executorService; diff --git a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/spi/package-info.java b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/spi/package-info.java index a675cf4d180..82c67541fa3 100644 --- a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/spi/package-info.java +++ b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/spi/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or 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,7 +16,7 @@ /** * Service provider interface for Helidon DB implementation for JDBC. * - * The main entry point for JDBC DB Client configuration interceptors implementation + * The main entry point for JDBC DB Client configuration services implementation * is {@link io.helidon.dbclient.jdbc.spi.HikariCpExtensionProvider}. */ package io.helidon.dbclient.jdbc.spi; diff --git a/dbclient/metrics/pom.xml b/dbclient/metrics/pom.xml index 9133265e1a3..02a85891634 100644 --- a/dbclient/metrics/pom.xml +++ b/dbclient/metrics/pom.xml @@ -39,5 +39,9 @@ io.helidon.metrics helidon-metrics + + io.helidon.dbclient + helidon-dbclient-common + diff --git a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbCounter.java b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientCounter.java similarity index 54% rename from dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbCounter.java rename to dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientCounter.java index 3d0edea22bd..f64901b6d0d 100644 --- a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbCounter.java +++ b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientCounter.java @@ -17,7 +17,7 @@ import java.util.concurrent.CompletionStage; -import io.helidon.config.Config; +import io.helidon.dbclient.DbClientService; import org.eclipse.microprofile.metrics.Counter; import org.eclipse.microprofile.metrics.Metadata; @@ -25,58 +25,34 @@ import org.eclipse.microprofile.metrics.MetricType; /** - * Counter metric for Helidon DB. This class implements the {@link io.helidon.dbclient.DbInterceptor} and + * Counter metric for Helidon DB. This class implements the {@link io.helidon.dbclient.DbClientService} and * can be configured either through a {@link io.helidon.dbclient.DbClient.Builder} or through configuration. */ -public final class DbCounter extends DbMetric { - private DbCounter(Builder builder) { +final class DbClientCounter extends DbClientMetric { + private DbClientCounter(Builder builder) { super(builder); } - /** - * Create a counter from configuration. - * - * @param config configuration to read - * @return a new counter - * @see io.helidon.dbclient.metrics.DbMetricBuilder#config(io.helidon.config.Config) - */ - public static DbCounter create(Config config) { - return builder().config(config).build(); - } - - /** - * Create a new counter using default configuration. - *

By default the name format is {@code db.counter.statement-name}, where {@code statement-name} - * is provided at runtime. - * - * @return a new counter - */ - public static DbCounter create() { - return builder().build(); - } - /** * Create a new fluent API builder to create a new counter metric. * @return a new builder instance */ - public static Builder builder() { + static Builder builder() { return new Builder(); } @Override protected void executeMetric(Counter metric, CompletionStage aFuture) { - aFuture - .thenAccept(nothing -> { - if (measureSuccess()) { - metric.inc(); - } - }) - .exceptionally(throwable -> { - if (measureErrors()) { - metric.inc(); - } - return null; - }); + aFuture.thenRun(() -> { + if (measureSuccess()) { + metric.inc(); + } + }).exceptionally(throwable -> { + if (measureErrors()) { + metric.inc(); + } + return null; + }); } @Override @@ -95,12 +71,12 @@ protected String defaultNamePrefix() { } /** - * Fluent API builder for {@link io.helidon.dbclient.metrics.DbCounter}. + * Fluent API builder for {@link DbClientCounter}. */ - public static class Builder extends DbMetricBuilder implements io.helidon.common.Builder { + static class Builder extends DbClientMetricBuilder { @Override - public DbCounter build() { - return new DbCounter(this); + public DbClientService build() { + return new DbClientCounter(this); } } } diff --git a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbMeter.java b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMeter.java similarity index 66% rename from dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbMeter.java rename to dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMeter.java index f0cb8096f09..7f794438421 100644 --- a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbMeter.java +++ b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMeter.java @@ -17,49 +17,25 @@ import java.util.concurrent.CompletionStage; -import io.helidon.config.Config; - import org.eclipse.microprofile.metrics.Metadata; import org.eclipse.microprofile.metrics.Meter; import org.eclipse.microprofile.metrics.MetricRegistry; import org.eclipse.microprofile.metrics.MetricType; /** - * Meter for Helidon DB. This class implements the {@link io.helidon.dbclient.DbInterceptor} and + * Meter for Helidon DB. This class implements the {@link io.helidon.dbclient.DbClientService} and * can be configured either through a {@link io.helidon.dbclient.DbClient.Builder} or through configuration. */ -public final class DbMeter extends DbMetric { - private DbMeter(Builder builder) { +final class DbClientMeter extends DbClientMetric { + private DbClientMeter(Builder builder) { super(builder); } - /** - * Create a meter from configuration. - * - * @param config configuration to read - * @return a new meter - * @see io.helidon.dbclient.metrics.DbMetricBuilder#config(io.helidon.config.Config) - */ - public static DbMeter create(Config config) { - return builder().config(config).build(); - } - - /** - * Create a new meter using default configuration. - *

By default the name format is {@code db.meter.statement-name}, where {@code statement-name} - * is provided at runtime. - * - * @return a new meter - */ - public static DbMeter create() { - return builder().build(); - } - /** * Create a new fluent API builder to create a new meter metric. * @return a new builder instance */ - public static Builder builder() { + static Builder builder() { return new Builder(); } @@ -95,12 +71,12 @@ protected String defaultNamePrefix() { } /** - * Fluent API builder for {@link io.helidon.dbclient.metrics.DbMeter}. + * Fluent API builder for {@link DbClientMeter}. */ - public static class Builder extends DbMetricBuilder implements io.helidon.common.Builder { + static class Builder extends DbClientMetricBuilder { @Override - public DbMeter build() { - return new DbMeter(this); + public DbClientMeter build() { + return new DbClientMeter(this); } } } diff --git a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbMetric.java b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetric.java similarity index 74% rename from dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbMetric.java rename to dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetric.java index dc65bce3043..7918c75c6f7 100644 --- a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbMetric.java +++ b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetric.java @@ -16,14 +16,14 @@ package io.helidon.dbclient.metrics; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; -import io.helidon.dbclient.DbInterceptor; -import io.helidon.dbclient.DbInterceptorContext; +import io.helidon.common.reactive.Single; +import io.helidon.dbclient.DbClientServiceContext; import io.helidon.dbclient.DbStatementType; +import io.helidon.dbclient.common.DbClientServiceBase; import io.helidon.metrics.RegistryFactory; import org.eclipse.microprofile.metrics.Metadata; @@ -35,7 +35,7 @@ /** * Common ancestor for Helidon DB metrics. */ -abstract class DbMetric implements DbInterceptor { +abstract class DbClientMetric extends DbClientServiceBase { private final Metadata meta; private final String description; private final BiFunction nameFunction; @@ -44,17 +44,19 @@ abstract class DbMetric implements DbInterceptor { private final boolean measureErrors; private final boolean measureSuccess; - protected DbMetric(DbMetricBuilder builder) { - BiFunction namedFunction = builder.nameFormat(); + protected DbClientMetric(DbClientMetricBuilderBase builder) { + super(builder); + + BiFunction nameFunction = builder.nameFormat(); this.meta = builder.meta(); - if (null == namedFunction) { - namedFunction = (name, statement) -> defaultNamePrefix() + name; + if (null == nameFunction) { + nameFunction = (name, statement) -> defaultNamePrefix() + name; } - this.nameFunction = namedFunction; + this.nameFunction = nameFunction; this.registry = RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.APPLICATION); - this.measureErrors = builder.measureErrors(); - this.measureSuccess = builder.measureSuccess(); + this.measureErrors = builder.errors(); + this.measureSuccess = builder.success(); String tmpDescription; if (builder.description() == null) { tmpDescription = ((null == meta) ? null : meta.getDescription().orElse(null)); @@ -67,9 +69,9 @@ protected DbMetric(DbMetricBuilder builder) { protected abstract String defaultNamePrefix(); @Override - public CompletableFuture statement(DbInterceptorContext interceptorContext) { - DbStatementType dbStatementType = interceptorContext.statementType(); - String statementName = interceptorContext.statementName(); + protected Single apply(DbClientServiceContext context) { + DbStatementType dbStatementType = context.statementType(); + String statementName = context.statementName(); T metric = cache.computeIfAbsent(statementName, s -> { String name = nameFunction.apply(statementName, dbStatementType); @@ -82,9 +84,9 @@ public CompletableFuture statement(DbInterceptorContext in return metric(registry, builder.build()); }); - executeMetric(metric, interceptorContext.statementFuture()); + executeMetric(metric, context.statementFuture()); - return CompletableFuture.completedFuture(interceptorContext); + return Single.just(context); } protected boolean measureErrors() { diff --git a/media/jsonp/server/src/main/java/module-info.java b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetricBuilder.java similarity index 62% rename from media/jsonp/server/src/main/java/module-info.java rename to dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetricBuilder.java index e67400408f1..9c02427ddc4 100644 --- a/media/jsonp/server/src/main/java/module-info.java +++ b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetricBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package io.helidon.dbclient.metrics; + +import io.helidon.common.Builder; +import io.helidon.dbclient.DbClientService; /** - * JSON-P support for Webserver. - * - * @see io.helidon.media.jsonp.server.JsonSupport + * DB Client metric builder. */ -module io.helidon.media.jsonp.server { - requires io.helidon.media.jsonp.common; - requires io.helidon.webserver; - - exports io.helidon.media.jsonp.server; +public abstract class DbClientMetricBuilder extends DbClientMetricBuilderBase + implements Builder { } diff --git a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbMetricBuilder.java b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetricBuilderBase.java similarity index 84% rename from dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbMetricBuilder.java rename to dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetricBuilderBase.java index 7a8142fb140..6944e4f25ec 100644 --- a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbMetricBuilder.java +++ b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetricBuilderBase.java @@ -21,6 +21,7 @@ import io.helidon.common.HelidonFlavor; import io.helidon.config.Config; import io.helidon.dbclient.DbStatementType; +import io.helidon.dbclient.common.DbClientServiceBase; import org.eclipse.microprofile.metrics.Metadata; @@ -28,7 +29,9 @@ * A metric builder used as a base for Helidon DB metrics. * @param type of the builder extending this class */ -public abstract class DbMetricBuilder> { +abstract class DbClientMetricBuilderBase> + extends DbClientServiceBase.DbClientServiceBuilderBase { + static { HelidonFeatures.register(HelidonFlavor.SE, "DbClient", "Metrics"); } @@ -63,15 +66,15 @@ public T metadata(Metadata meta) { /** * Configure a name format. - *

The format can use up to two parameters - first is the statement name, second the {@link io.helidon.dbclient.DbStatementType} - * as a string. + *

The format can use up to two parameters - first is the {@link io.helidon.dbclient.DbStatementType} + * as a string, second is the statement name. * * @param format format string expecting zero to two parameters that can be processed by * {@link String#format(String, Object...)} * @return updated builder instance */ public T nameFormat(String format) { - return nameFormat((name, queryType) -> String.format(format, name, queryType.toString())); + return nameFormat((name, queryType) -> String.format(format, queryType.toString(), name)); } /** @@ -89,22 +92,22 @@ public T nameFormat(BiFunction function) { /** * Whether to measure failed statements. * - * @param shouldWe set to {@code false} if errors should be ignored + * @param measureErrors set to {@code false} if errors should be ignored * @return updated builder instance */ - public T measureErrors(boolean shouldWe) { - this.measureErrors = shouldWe; + public T errors(boolean measureErrors) { + this.measureErrors = measureErrors; return me(); } /** * Whether to measure successful statements. * - * @param shouldWe set to {@code false} if successes should be ignored + * @param measureSuccess set to {@code false} if successes should be ignored * @return updated builder instance */ - public T measureSuccess(boolean shouldWe) { - this.measureSuccess = shouldWe; + public T success(boolean measureSuccess) { + this.measureSuccess = measureSuccess; return me(); } @@ -156,8 +159,9 @@ public T description(String description) { * @return updated builder instance */ public T config(Config config) { - config.get("errors").asBoolean().ifPresent(this::measureErrors); - config.get("success").asBoolean().ifPresent(this::measureSuccess); + super.config(config); + config.get("errors").asBoolean().ifPresent(this::errors); + config.get("success").asBoolean().ifPresent(this::success); config.get("name-format").asString().ifPresent(this::nameFormat); config.get("description").asString().ifPresent(this::description); return me(); @@ -180,11 +184,11 @@ BiFunction nameFormat() { return nameFormat; } - boolean measureErrors() { + boolean errors() { return measureErrors; } - boolean measureSuccess() { + boolean success() { return measureSuccess; } } diff --git a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetrics.java b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetrics.java new file mode 100644 index 00000000000..0cc406ae5db --- /dev/null +++ b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetrics.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.dbclient.metrics; + +/** + * Utility class to obtain various types of metrics to register + * with {@link io.helidon.dbclient.DbClient.Builder#addService(io.helidon.dbclient.DbClientService)}. + * Metrics can be limited to a set of statement types or statement names, and also configured to + * meter success, failure or both. + * + * @see io.helidon.dbclient.metrics.DbClientMetricBuilder#statementTypes(io.helidon.dbclient.DbStatementType...) + * @see io.helidon.dbclient.metrics.DbClientMetricBuilder#statementNames(String...) + * @see io.helidon.dbclient.metrics.DbClientMetricBuilder#statementPredicate(java.util.function.Predicate) + * @see io.helidon.dbclient.metrics.DbClientMetricBuilder#success(boolean) + * @see io.helidon.dbclient.metrics.DbClientMetricBuilder#errors(boolean) + */ +public class DbClientMetrics { + private DbClientMetrics() { + } + + /** + * Create a counter builder, to be registered + * with {@link io.helidon.dbclient.DbClient.Builder#addService(java.util.function.Supplier)}. + * + * @return a new counter builder + * @see org.eclipse.microprofile.metrics.Counter + */ + public static DbClientMetricBuilder counter() { + return DbClientCounter.builder(); + } + + /** + * Create a meter builder, to be registered + * with {@link io.helidon.dbclient.DbClient.Builder#addService(java.util.function.Supplier)}. + * + * @return a new meter builder + * @see org.eclipse.microprofile.metrics.Meter + */ + public static DbClientMetricBuilder meter() { + return DbClientMeter.builder(); + } + + /** + * Create a timer builder, to be registered + * with {@link io.helidon.dbclient.DbClient.Builder#addService(java.util.function.Supplier)}. + * + * @return a new timer builder + * @see org.eclipse.microprofile.metrics.Timer + */ + public static DbClientMetricBuilder timer() { + return DbClientTimer.builder(); + } +} diff --git a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetricsProvider.java b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetricsProvider.java new file mode 100644 index 00000000000..d7069be98f5 --- /dev/null +++ b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientMetricsProvider.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * + * 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.dbclient.metrics; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Logger; + +import io.helidon.config.Config; +import io.helidon.dbclient.DbClientException; +import io.helidon.dbclient.DbClientService; +import io.helidon.dbclient.spi.DbClientServiceProvider; + +/** + * Java service loader service for DB metrics. + */ +public class DbClientMetricsProvider implements DbClientServiceProvider { + private static final Logger LOGGER = Logger.getLogger(DbClientMetricsProvider.class.getName()); + + @Override + public String configKey() { + return "metrics"; + } + + @Override + public Collection create(Config config) { + List metricConfigs = config.asNodeList().orElseGet(List::of); + List result = new LinkedList<>(); + + for (Config metricConfig : metricConfigs) { + result.add(fromConfig(metricConfig)); + } + + if (result.isEmpty()) { + LOGGER.info("DB Client metrics are enabled, yet none are configured in config."); + } + + return result; + } + + private DbClientService fromConfig(Config config) { + String type = config.get("type").asString().orElse("COUNTER"); + switch (type) { + case "COUNTER": + return DbClientMetrics.counter().config(config).build(); + case "METER": + return DbClientMetrics.meter().config(config).build(); + case "TIMER": + return DbClientMetrics.timer().config(config).build(); + default: + throw new DbClientException("Metrics type " + type + " is not supported through service loader"); + } + } +} diff --git a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbTimer.java b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientTimer.java similarity index 68% rename from dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbTimer.java rename to dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientTimer.java index fd4e4641ae2..66e0a2f797d 100644 --- a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbTimer.java +++ b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbClientTimer.java @@ -18,50 +18,26 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; -import io.helidon.config.Config; - import org.eclipse.microprofile.metrics.Metadata; import org.eclipse.microprofile.metrics.MetricRegistry; import org.eclipse.microprofile.metrics.MetricType; import org.eclipse.microprofile.metrics.Timer; /** - * Timer metric for Helidon DB. This class implements the {@link io.helidon.dbclient.DbInterceptor} and + * Timer metric for Helidon DB. This class implements the {@link io.helidon.dbclient.DbClientService} and * can be configured either through a {@link io.helidon.dbclient.DbClient.Builder} or through configuration. */ -public final class DbTimer extends DbMetric { +final class DbClientTimer extends DbClientMetric { - private DbTimer(Builder builder) { + private DbClientTimer(Builder builder) { super(builder); } - /** - * Create a timer from configuration. - * - * @param config configuration to read - * @return a new timer - * @see io.helidon.dbclient.metrics.DbMetricBuilder#config(io.helidon.config.Config) - */ - public static DbTimer create(Config config) { - return builder().config(config).build(); - } - - /** - * Create a new timer using default configuration. - *

By default the name format is {@code db.timer.statement-name}, where {@code statement-name} - * is provided at runtime. - * - * @return a new timer - */ - public static DbTimer create() { - return builder().build(); - } - /** * Create a new fluent API builder to create a new timer metric. * @return a new builder instance */ - public static Builder builder() { + static Builder builder() { return new Builder(); } @@ -104,12 +80,12 @@ protected Timer metric(MetricRegistry registry, Metadata meta) { } /** - * Fluent API builder for {@link io.helidon.dbclient.metrics.DbTimer}. + * Fluent API builder for {@link DbClientTimer}. */ - public static class Builder extends DbMetricBuilder implements io.helidon.common.Builder { + static class Builder extends DbClientMetricBuilder { @Override - public DbTimer build() { - return new DbTimer(this); + public DbClientTimer build() { + return new DbClientTimer(this); } } } diff --git a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbMetricsProvider.java b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbMetricsProvider.java deleted file mode 100644 index 141d61960bd..00000000000 --- a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/DbMetricsProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.dbclient.metrics; - -import io.helidon.config.Config; -import io.helidon.dbclient.DbClientException; -import io.helidon.dbclient.DbInterceptor; -import io.helidon.dbclient.spi.DbInterceptorProvider; - -/** - * Service for DB metrics. - */ -public class DbMetricsProvider implements DbInterceptorProvider { - @Override - public String configKey() { - return "metrics"; - } - - @Override - public DbInterceptor create(Config config) { - String type = config.get("type").asString().orElse("COUNTER"); - switch (type) { - case "COUNTER": - return DbCounter.create(config); - case "METER": - return DbMeter.create(config); - case "TIMER": - return DbTimer.create(config); - default: - throw new DbClientException("Metrics type " + type + " is not supported through service loader"); - } - } -} diff --git a/dbclient/metrics/src/main/java/module-info.java b/dbclient/metrics/src/main/java/module-info.java index f77e5919a7b..ff7ca0ab740 100644 --- a/dbclient/metrics/src/main/java/module-info.java +++ b/dbclient/metrics/src/main/java/module-info.java @@ -14,6 +14,9 @@ * limitations under the License. */ +import io.helidon.dbclient.metrics.DbClientMetricsProvider; +import io.helidon.dbclient.spi.DbClientServiceProvider; + /** * Helidon DB Client Metrics. */ @@ -21,8 +24,9 @@ requires java.logging; requires io.helidon.dbclient; requires io.helidon.metrics; + requires io.helidon.dbclient.common; exports io.helidon.dbclient.metrics; - provides io.helidon.dbclient.spi.DbInterceptorProvider with io.helidon.dbclient.metrics.DbMetricsProvider; + provides DbClientServiceProvider with DbClientMetricsProvider; } diff --git a/dbclient/metrics/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbInterceptorProvider b/dbclient/metrics/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbClientServiceProvider similarity index 92% rename from dbclient/metrics/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbInterceptorProvider rename to dbclient/metrics/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbClientServiceProvider index 9830fbc15c5..4c00a7b3652 100644 --- a/dbclient/metrics/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbInterceptorProvider +++ b/dbclient/metrics/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbClientServiceProvider @@ -14,4 +14,4 @@ # limitations under the License. # -io.helidon.dbclient.metrics.DbMetricsProvider +io.helidon.dbclient.metrics.DbClientMetricsProvider diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbClient.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbClient.java index e898c405db3..f819b6b1c15 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbClient.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbClient.java @@ -20,15 +20,12 @@ import io.helidon.common.HelidonFeatures; import io.helidon.common.HelidonFlavor; -import io.helidon.common.mapper.MapperManager; import io.helidon.common.reactive.Single; import io.helidon.common.reactive.Subscribable; import io.helidon.dbclient.DbClient; import io.helidon.dbclient.DbExecute; -import io.helidon.dbclient.DbMapperManager; -import io.helidon.dbclient.DbStatements; import io.helidon.dbclient.DbTransaction; -import io.helidon.dbclient.common.InterceptorSupport; +import io.helidon.dbclient.common.DbClientContext; import com.mongodb.ConnectionString; import com.mongodb.MongoClientSettings; @@ -48,13 +45,10 @@ public class MongoDbClient implements DbClient { } private final MongoDbClientConfig config; - private final DbStatements statements; private final MongoClient client; private final MongoDatabase db; - private final MapperManager mapperManager; - private final DbMapperManager dbMapperManager; private final ConnectionString connectionString; - private final InterceptorSupport interceptors; + private final DbClientContext clientContext; /** * Creates an instance of MongoDB driver handler. @@ -62,14 +56,17 @@ public class MongoDbClient implements DbClient { * @param builder builder for mongoDB database */ MongoDbClient(MongoDbClientProviderBuilder builder) { + this.clientContext = DbClientContext.builder() + .dbMapperManager(builder.dbMapperManager()) + .mapperManager(builder.mapperManager()) + .clientServices(builder.clientServices()) + .statements(builder.statements()) + .build(); + this.config = builder.dbConfig(); this.connectionString = new ConnectionString(config.url()); - this.statements = builder.statements(); - this.mapperManager = builder.mapperManager(); - this.dbMapperManager = builder.dbMapperManager(); this.client = initMongoClient(); this.db = initMongoDatabase(); - this.interceptors = builder.interceptors(); } private static final class MongoSessionSubscriber implements org.reactivestreams.Subscriber { @@ -116,7 +113,7 @@ public > T inTransaction(Function // client.startSession().subscribe(new MongoSessionSubscriber(txFuture)); // return txFuture.thenCompose(tx -> { // MongoDbTransaction mongoTx = new MongoDbTransaction( - // db, tx, statements, dbMapperManager, mapperManager, interceptors); + // db, tx, statements, dbMapperManager, mapperManager, services); // CompletionStage future = executor.apply(mongoTx); // // FIXME: Commit and rollback return Publisher so another future must be introduced here // // to cover commit or rollback. This future may be passed using allRegistered call @@ -128,7 +125,7 @@ public > T inTransaction(Function @Override public > T execute(Function executor) { - return executor.apply(new MongoDbExecute(db, statements, dbMapperManager, mapperManager, interceptors)); + return executor.apply(new MongoDbExecute(db, clientContext)); } @Override diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbClientProviderBuilder.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbClientProviderBuilder.java index 01722275efb..3cc8085402c 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbClientProviderBuilder.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbClientProviderBuilder.java @@ -15,6 +15,8 @@ */ package io.helidon.dbclient.mongodb; +import java.util.LinkedList; +import java.util.List; import java.util.Optional; import io.helidon.common.GenericType; @@ -22,12 +24,10 @@ import io.helidon.config.Config; import io.helidon.dbclient.DbClient; import io.helidon.dbclient.DbClientException; -import io.helidon.dbclient.DbInterceptor; +import io.helidon.dbclient.DbClientService; import io.helidon.dbclient.DbMapper; import io.helidon.dbclient.DbMapperManager; -import io.helidon.dbclient.DbStatementType; import io.helidon.dbclient.DbStatements; -import io.helidon.dbclient.common.InterceptorSupport; import io.helidon.dbclient.spi.DbClientProviderBuilder; import io.helidon.dbclient.spi.DbMapperProvider; @@ -36,7 +36,7 @@ */ public final class MongoDbClientProviderBuilder implements DbClientProviderBuilder { - private final InterceptorSupport.Builder interceptors = InterceptorSupport.builder(); + private final List clientServices = new LinkedList<>(); private final DbMapperManager.Builder dbMapperBuilder = DbMapperManager.builder(); private String url; @@ -118,20 +118,8 @@ public MongoDbClientProviderBuilder statements(DbStatements statements) { } @Override - public MongoDbClientProviderBuilder addInterceptor(DbInterceptor interceptor) { - this.interceptors.add(interceptor); - return this; - } - - @Override - public MongoDbClientProviderBuilder addInterceptor(DbInterceptor interceptor, String... statementNames) { - this.interceptors.add(interceptor, statementNames); - return this; - } - - @Override - public MongoDbClientProviderBuilder addInterceptor(DbInterceptor interceptor, DbStatementType... dbStatementTypes) { - this.interceptors.add(interceptor, dbStatementTypes); + public MongoDbClientProviderBuilder addService(DbClientService clientService) { + this.clientServices.add(clientService); return this; } @@ -182,8 +170,8 @@ public MongoDbClientProviderBuilder addMapperProvider(DbMapperProvider provider) return this; } - InterceptorSupport interceptors() { - return interceptors.build(); + List clientServices() { + return List.copyOf(clientServices); } DbMapperManager.Builder dbMapperBuilder() { diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbCommandExecutor.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbCommandExecutor.java index 8d22c17e246..f26224c30fa 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbCommandExecutor.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbCommandExecutor.java @@ -24,9 +24,10 @@ import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; -import io.helidon.dbclient.DbInterceptorContext; +import io.helidon.dbclient.DbClientServiceContext; import io.helidon.dbclient.DbRow; import io.helidon.dbclient.DbStatementType; +import io.helidon.dbclient.common.DbClientContext; import com.mongodb.reactivestreams.client.MongoDatabase; import org.bson.Document; @@ -48,7 +49,7 @@ private MongoDbCommandExecutor() { } static Multi executeCommand(MongoDbStatement dbStatement, - CompletionStage dbContextFuture, + CompletionStage dbContextFuture, CompletableFuture statementFuture, CompletableFuture commandFuture) { @@ -77,7 +78,7 @@ private static Multi executeCommandInMongoDB(MongoDbStatement dbStatement CompletableFuture statementFuture, CompletableFuture commandFuture) { - return Single.from(stmtFuture) + return Single.create(stmtFuture) .flatMap(mongoStmt -> callStatement(dbStatement, mongoStmt, statementFuture, commandFuture)); } @@ -104,6 +105,7 @@ static final class CommandRows { private final AtomicBoolean resultRequested = new AtomicBoolean(false); private final Publisher publisher; + private final DbClientContext clientContext; private final MongoDbStatement dbStatement; private final CompletableFuture statementFuture; private final CompletableFuture commandFuture; @@ -112,6 +114,7 @@ static final class CommandRows { MongoDbStatement dbStatement, CompletableFuture statementFuture, CompletableFuture commandFuture) { + this.clientContext = dbStatement.clientContext(); this.publisher = publisher; this.dbStatement = dbStatement; this.statementFuture = statementFuture; @@ -124,10 +127,10 @@ public Flow.Publisher publisher() { } private Flow.Publisher toDbPublisher() { - MongoDbQueryProcessor qp = new MongoDbQueryProcessor( - dbStatement, - statementFuture, - commandFuture); + MongoDbQueryProcessor qp = new MongoDbQueryProcessor(clientContext, + dbStatement, + statementFuture, + commandFuture); publisher.subscribe(qp); return qp; } diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbDMLExecutor.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbDMLExecutor.java index beb8d401321..e4d8fb23675 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbDMLExecutor.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbDMLExecutor.java @@ -20,7 +20,7 @@ import java.util.concurrent.atomic.LongAdder; import java.util.logging.Logger; -import io.helidon.dbclient.DbInterceptorContext; +import io.helidon.dbclient.DbClientServiceContext; import io.helidon.dbclient.DbStatementType; import com.mongodb.client.result.DeleteResult; @@ -48,7 +48,7 @@ static CompletionStage executeDml( MongoDbStatement dbStatement, DbStatementType dbStatementType, MongoDbStatement.MongoStatement mongoStatement, - CompletionStage dbContextFuture, + CompletionStage dbContextFuture, CompletableFuture statementFuture, CompletableFuture queryFuture ) { diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbExecute.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbExecute.java index 153f2b17d8b..031e0c331da 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbExecute.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbExecute.java @@ -15,16 +15,14 @@ */ package io.helidon.dbclient.mongodb; -import io.helidon.common.mapper.MapperManager; import io.helidon.dbclient.DbExecute; -import io.helidon.dbclient.DbMapperManager; import io.helidon.dbclient.DbStatementDml; import io.helidon.dbclient.DbStatementGet; import io.helidon.dbclient.DbStatementQuery; import io.helidon.dbclient.DbStatementType; -import io.helidon.dbclient.DbStatements; import io.helidon.dbclient.common.AbstractDbExecute; -import io.helidon.dbclient.common.InterceptorSupport; +import io.helidon.dbclient.common.DbClientContext; +import io.helidon.dbclient.common.DbStatementContext; import com.mongodb.reactivestreams.client.MongoDatabase; @@ -33,60 +31,67 @@ */ public class MongoDbExecute extends AbstractDbExecute implements DbExecute { - private final DbMapperManager dbMapperManager; - private final MapperManager mapperManager; - private final InterceptorSupport interceptors; private final MongoDatabase db; + private final DbClientContext clientContext; MongoDbExecute(MongoDatabase db, - DbStatements statements, - DbMapperManager dbMapperManager, - MapperManager mapperManager, - InterceptorSupport interceptors) { - super(statements); + DbClientContext clientContext) { + super(clientContext.statements()); this.db = db; - this.dbMapperManager = dbMapperManager; - this.mapperManager = mapperManager; - this.interceptors = interceptors; + this.clientContext = clientContext; } @Override public DbStatementQuery createNamedQuery(String statementName, String statement) { - return new MongoDbStatementQuery(DbStatementType.QUERY, - db, - statementName, - statement, - dbMapperManager, - mapperManager, - interceptors); + return new MongoDbStatementQuery(db, + DbStatementContext.create(clientContext, + DbStatementType.QUERY, + statementName, + statement)); } @Override public DbStatementGet createNamedGet(String statementName, String statement) { - return new MongoDbStatementGet(db, statementName, statement, dbMapperManager, mapperManager, interceptors); + return new MongoDbStatementGet(db, + DbStatementContext.create(clientContext, + DbStatementType.GET, + statementName, + statement)); } @Override public DbStatementDml createNamedDmlStatement(String statementName, String statement) { - return new MongoDbStatementDml(DbStatementType.DML, db, statementName, statement, dbMapperManager, mapperManager, - interceptors); + return new MongoDbStatementDml(db, + DbStatementContext.create(clientContext, + DbStatementType.DML, + statementName, + statement)); } @Override public DbStatementDml createNamedInsert(String statementName, String statement) { - return new MongoDbStatementDml(DbStatementType.INSERT, db, statementName, statement, dbMapperManager, mapperManager, - interceptors); + return new MongoDbStatementDml(db, + DbStatementContext.create(clientContext, + DbStatementType.INSERT, + statementName, + statement)); } @Override public DbStatementDml createNamedUpdate(String statementName, String statement) { - return new MongoDbStatementDml(DbStatementType.UPDATE, db, statementName, statement, dbMapperManager, mapperManager, - interceptors); + return new MongoDbStatementDml(db, + DbStatementContext.create(clientContext, + DbStatementType.UPDATE, + statementName, + statement)); } @Override public DbStatementDml createNamedDelete(String statementName, String statement) { - return new MongoDbStatementDml(DbStatementType.DELETE, db, statementName, statement, dbMapperManager, mapperManager, - interceptors); + return new MongoDbStatementDml(db, + DbStatementContext.create(clientContext, + DbStatementType.DELETE, + statementName, + statement)); } - } +} diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbQueryExecutor.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbQueryExecutor.java deleted file mode 100644 index 75c85512fdb..00000000000 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbQueryExecutor.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or 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.dbclient.mongodb; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.logging.Logger; - -import io.helidon.common.reactive.Multi; -import io.helidon.common.reactive.Single; -import io.helidon.dbclient.DbInterceptorContext; -import io.helidon.dbclient.DbRow; -import io.helidon.dbclient.DbStatementType; - -import com.mongodb.reactivestreams.client.FindPublisher; -import com.mongodb.reactivestreams.client.MongoCollection; -import org.bson.Document; - -import static io.helidon.dbclient.mongodb.MongoDbStatement.READER_FACTORY; - -/** - * Executes Mongo specific query and returns result. - * Utility class with static methods only. - */ -final class MongoDbQueryExecutor { - - /** Local logger instance. */ - private static final Logger LOGGER = Logger.getLogger(MongoDbStatementQuery.class.getName()); - - private MongoDbQueryExecutor() { - throw new UnsupportedOperationException("Utility class MongoDbQueryExecutor instances are not allowed!"); - } - - static Multi executeQuery(MongoDbStatement dbStatement, - CompletionStage dbContextFuture, - CompletableFuture statementFuture, - CompletableFuture queryFuture) { - - dbContextFuture.exceptionally(throwable -> { - statementFuture.completeExceptionally(throwable); - queryFuture.completeExceptionally(throwable); - return null; - }); - - String statement = dbStatement.build(); - MongoDbStatement.MongoStatement stmt; - try { - stmt = new MongoDbStatement.MongoStatement(DbStatementType.QUERY, READER_FACTORY, statement); - } catch (IllegalStateException e) { - // maybe this is a command? - try { - stmt = new MongoDbStatement.MongoStatement(DbStatementType.COMMAND, READER_FACTORY, statement); - } catch (IllegalStateException ignored) { - // we want to report the original exception - throw e; - } - } - - if (stmt.getOperation() != MongoDbStatement.MongoOperation.QUERY) { - if (stmt.getOperation() == MongoDbStatement.MongoOperation.COMMAND) { - return MongoDbCommandExecutor.executeCommand(dbStatement, - dbContextFuture, - statementFuture, - queryFuture); - } else { - return Multi.error(new UnsupportedOperationException( - String.format("Operation %s is not supported by query", stmt.getOperation().toString()))); - } - } - // we reach here only for query statements - - // final variable required for lambda - MongoDbStatement.MongoStatement usedStatement = stmt; - return Single.from(dbContextFuture) - .flatMap(it -> callStatement(dbStatement, usedStatement, statementFuture, queryFuture)); - } - - private static Multi callStatement(MongoDbStatement dbStatement, - MongoDbStatement.MongoStatement mongoStmt, - CompletableFuture statementFuture, - CompletableFuture queryFuture) { - - final MongoCollection mc = dbStatement.db() - .getCollection(mongoStmt.getCollection()); - final Document query = mongoStmt.getQuery(); - final Document projection = mongoStmt.getProjection(); - LOGGER.fine(() -> String.format( - "Query: %s, Projection: %s", query.toString(), (projection != null ? projection : "N/A"))); - FindPublisher publisher = dbStatement.noTx() - ? mc.find(query) - : mc.find(dbStatement.txManager().tx(), query); - if (projection != null) { - publisher = publisher.projection(projection); - } - - return Multi.from(new MongoDbRows<>(publisher, - dbStatement, - DbRow.class, - statementFuture, - queryFuture) - .publisher()); - } -} diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbQueryProcessor.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbQueryProcessor.java index 875f0c30bb2..42863890614 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbQueryProcessor.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbQueryProcessor.java @@ -22,6 +22,7 @@ import java.util.logging.Logger; import io.helidon.dbclient.DbRow; +import io.helidon.dbclient.common.DbClientContext; import org.bson.Document; import org.reactivestreams.Subscription; @@ -38,14 +39,16 @@ final class MongoDbQueryProcessor implements org.reactivestreams.Subscriber queryFuture; private final MongoDbStatement dbStatement; private final CompletableFuture statementFuture; + private final DbClientContext clientContext; + private Flow.Subscriber subscriber; private Subscription subscription; - MongoDbQueryProcessor( - MongoDbStatement dbStatement, - CompletableFuture statementFuture, - CompletableFuture queryFuture - ) { + MongoDbQueryProcessor(DbClientContext clientContext, + MongoDbStatement dbStatement, + CompletableFuture statementFuture, + CompletableFuture queryFuture) { + this.clientContext = clientContext; this.statementFuture = statementFuture; this.queryFuture = queryFuture; this.dbStatement = dbStatement; @@ -58,11 +61,11 @@ public void onSubscribe(Subscription subscription) { @Override public void onNext(Document doc) { - MongoDbRow dbRow = new MongoDbRow(dbStatement.dbMapperManager(), dbStatement.mapperManager(), doc.size()); + MongoDbRow dbRow = new MongoDbRow(clientContext.dbMapperManager(), clientContext.mapperManager(), doc.size()); doc.forEach((name, value) -> { LOGGER.finest(() -> String.format( "Column name = %s, value = %s", name, (value != null ? value.toString() : "N/A"))); - dbRow.add(name, new MongoDbColumn(dbStatement.dbMapperManager(), dbStatement.mapperManager(), name, value)); + dbRow.add(name, new MongoDbColumn(clientContext.dbMapperManager(), clientContext.mapperManager(), name, value)); }); count.incrementAndGet(); subscriber.onNext(dbRow); diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbRows.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbRows.java index 633e78a5725..7dea49b9b34 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbRows.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbRows.java @@ -24,6 +24,7 @@ import io.helidon.common.GenericType; import io.helidon.common.reactive.Multi; import io.helidon.dbclient.DbRow; +import io.helidon.dbclient.common.DbClientContext; import com.mongodb.reactivestreams.client.FindPublisher; import org.bson.Document; @@ -36,6 +37,7 @@ public final class MongoDbRows { private final AtomicBoolean resultRequested = new AtomicBoolean(); + private DbClientContext clientContext; private final FindPublisher documentFindPublisher; private final MongoDbStatement dbStatement; private final CompletableFuture queryFuture; @@ -44,11 +46,14 @@ public final class MongoDbRows { private final MongoDbRows parent; private final CompletableFuture statementFuture; - MongoDbRows(FindPublisher documentFindPublisher, + MongoDbRows(DbClientContext clientContext, + FindPublisher documentFindPublisher, MongoDbStatement dbStatement, Class initialType, CompletableFuture statementFuture, CompletableFuture queryFuture) { + + this.clientContext = clientContext; this.documentFindPublisher = documentFindPublisher; this.dbStatement = dbStatement; this.statementFuture = statementFuture; @@ -58,22 +63,6 @@ public final class MongoDbRows { this.parent = null; } - private MongoDbRows(FindPublisher documentFindPublisher, - MongoDbStatement dbStatement, - CompletableFuture statementFuture, - CompletableFuture queryFuture, - GenericType nextType, - Function resultMapper, - MongoDbRows parent) { - this.documentFindPublisher = documentFindPublisher; - this.dbStatement = dbStatement; - this.statementFuture = statementFuture; - this.queryFuture = queryFuture; - this.resultMapper = resultMapper; - this.currentType = nextType; - this.parent = parent; - } - Flow.Publisher publisher() { checkResult(); @@ -90,14 +79,14 @@ private Flow.Publisher toPublisher() { Flow.Publisher parentPublisher = parent.publisher(); Function mappingFunction = (Function) resultMapper; // otherwise we must apply mapping - return Multi.from(parentPublisher).map(mappingFunction::apply); + return Multi.create(parentPublisher).map(mappingFunction::apply); } private Flow.Publisher toDbPublisher() { - MongoDbQueryProcessor qp = new MongoDbQueryProcessor( - dbStatement, - statementFuture, - queryFuture); + MongoDbQueryProcessor qp = new MongoDbQueryProcessor(clientContext, + dbStatement, + statementFuture, + queryFuture); documentFindPublisher.subscribe(qp); return qp; diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatement.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatement.java index 9d524ef1dc8..bfb54efde26 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatement.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatement.java @@ -22,12 +22,10 @@ import javax.json.Json; import javax.json.JsonReaderFactory; -import io.helidon.common.mapper.MapperManager; -import io.helidon.dbclient.DbMapperManager; import io.helidon.dbclient.DbStatement; import io.helidon.dbclient.DbStatementType; import io.helidon.dbclient.common.AbstractStatement; -import io.helidon.dbclient.common.InterceptorSupport; +import io.helidon.dbclient.common.DbStatementContext; import io.helidon.dbclient.mongodb.MongoDbTransaction.TransactionManager; import com.mongodb.reactivestreams.client.MongoDatabase; @@ -79,28 +77,11 @@ abstract class MongoDbStatement, R> extends Abstract /** * Creates an instance of MongoDB statement builder. * - * @param dbStatementType type of this statement - * @param db mongo database handler - * @param statementName name of this statement - * @param statement text of this statement - * @param dbMapperManager db mapper manager to use when mapping types to parameters - * @param mapperManager mapper manager to use when mapping results - * @param interceptors interceptors to be executed + * @param db mongo database handler + * @param statementContext configuration of statement */ - MongoDbStatement(DbStatementType dbStatementType, - MongoDatabase db, - String statementName, - String statement, - DbMapperManager dbMapperManager, - MapperManager mapperManager, - InterceptorSupport interceptors) { - - super(dbStatementType, - statementName, - statement, - dbMapperManager, - mapperManager, - interceptors); + MongoDbStatement(MongoDatabase db, DbStatementContext statementContext) { + super(statementContext); this.db = db; this.txManager = null; @@ -156,26 +137,6 @@ TransactionManager txManager() { return txManager; } - /** - * Db mapper manager. - * - * @return mapper manager for DB types - */ - @Override - protected DbMapperManager dbMapperManager() { - return super.dbMapperManager(); - } - - /** - * Mapper manager. - * - * @return generic mapper manager - */ - @Override - protected MapperManager mapperManager() { - return super.mapperManager(); - } - @Override protected String dbType() { return MongoDbClientProvider.DB_TYPE; diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementDml.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementDml.java index 3cd3ac83da8..443039d05cb 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementDml.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementDml.java @@ -16,15 +16,12 @@ package io.helidon.dbclient.mongodb; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import io.helidon.common.mapper.MapperManager; import io.helidon.common.reactive.Single; -import io.helidon.dbclient.DbInterceptorContext; -import io.helidon.dbclient.DbMapperManager; +import io.helidon.dbclient.DbClientServiceContext; import io.helidon.dbclient.DbStatementDml; import io.helidon.dbclient.DbStatementType; -import io.helidon.dbclient.common.InterceptorSupport; +import io.helidon.dbclient.common.DbStatementContext; import com.mongodb.reactivestreams.client.MongoDatabase; @@ -36,23 +33,9 @@ public class MongoDbStatementDml extends MongoDbStatement execute() { } @Override - protected Single doExecute( - CompletionStage dbContextFuture, - CompletableFuture statementFuture, - CompletableFuture queryFuture - ) { - return Single.from(MongoDbDMLExecutor.executeDml( + protected Single doExecute(Single dbContext, + CompletableFuture statementFuture, + CompletableFuture queryFuture) { + + return Single.create(MongoDbDMLExecutor.executeDml( this, dbStatementType, statement, - dbContextFuture, + dbContext, statementFuture, queryFuture)); } diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementGet.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementGet.java index 8fb0f730712..109a2fe0e99 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementGet.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementGet.java @@ -19,13 +19,10 @@ import java.util.Map; import java.util.Optional; -import io.helidon.common.mapper.MapperManager; import io.helidon.common.reactive.Single; -import io.helidon.dbclient.DbMapperManager; import io.helidon.dbclient.DbRow; import io.helidon.dbclient.DbStatementGet; -import io.helidon.dbclient.DbStatementType; -import io.helidon.dbclient.common.InterceptorSupport; +import io.helidon.dbclient.common.DbStatementContext; import io.helidon.dbclient.mongodb.MongoDbTransaction.TransactionManager; import com.mongodb.reactivestreams.client.MongoDatabase; @@ -37,19 +34,8 @@ public class MongoDbStatementGet implements DbStatementGet { private final MongoDbStatementQuery theQuery; - MongoDbStatementGet(MongoDatabase db, - String statementName, - String statement, - DbMapperManager dbMapperManager, - MapperManager mapperManager, - InterceptorSupport interceptors) { - this.theQuery = new MongoDbStatementQuery(DbStatementType.GET, - db, - statementName, - statement, - dbMapperManager, - mapperManager, - interceptors); + MongoDbStatementGet(MongoDatabase db, DbStatementContext statementContext) { + this.theQuery = new MongoDbStatementQuery(db, statementContext); } @Override @@ -90,7 +76,7 @@ public MongoDbStatementGet addParam(String name, Object parameter) { @Override public Single> execute() { - return Single.from(theQuery.execute()) + return Single.create(theQuery.execute()) .toOptionalSingle(); } diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementQuery.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementQuery.java index 287ddf0b32d..5829a7f88af 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementQuery.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementQuery.java @@ -16,51 +16,107 @@ package io.helidon.dbclient.mongodb; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; +import java.util.logging.Logger; -import io.helidon.common.mapper.MapperManager; import io.helidon.common.reactive.Multi; -import io.helidon.dbclient.DbInterceptorContext; -import io.helidon.dbclient.DbMapperManager; +import io.helidon.common.reactive.Single; +import io.helidon.dbclient.DbClientServiceContext; import io.helidon.dbclient.DbRow; import io.helidon.dbclient.DbStatementQuery; import io.helidon.dbclient.DbStatementType; -import io.helidon.dbclient.common.InterceptorSupport; +import io.helidon.dbclient.common.DbStatementContext; +import com.mongodb.reactivestreams.client.FindPublisher; +import com.mongodb.reactivestreams.client.MongoCollection; import com.mongodb.reactivestreams.client.MongoDatabase; +import org.bson.Document; /** * Implementation of a query for MongoDB. */ class MongoDbStatementQuery extends MongoDbStatement> implements DbStatementQuery { + private static final Logger LOGGER = Logger.getLogger(MongoDbStatementQuery.class.getName()); - MongoDbStatementQuery(DbStatementType dbStatementType, - MongoDatabase db, - String statementName, - String statement, - DbMapperManager dbMapperManager, - MapperManager mapperManager, - InterceptorSupport interceptors) { - - super(dbStatementType, - db, - statementName, - statement, - dbMapperManager, - mapperManager, - interceptors); + MongoDbStatementQuery(MongoDatabase db, DbStatementContext statementContext) { + super(db, statementContext); } @Override - protected Multi doExecute(CompletionStage dbContextFuture, + protected Multi doExecute(Single dbContextFuture, CompletableFuture statementFuture, CompletableFuture queryFuture) { - return MongoDbQueryExecutor.executeQuery( - this, - dbContextFuture, - statementFuture, - queryFuture); + dbContextFuture.exceptionally(throwable -> { + statementFuture.completeExceptionally(throwable); + queryFuture.completeExceptionally(throwable); + return null; + }); + + String statement = build(); + MongoDbStatement.MongoStatement stmt; + + try { + stmt = queryOrCommand(statement); + } catch (Exception e) { + return Multi.error(e); + } + + if (stmt.getOperation() != MongoDbStatement.MongoOperation.QUERY) { + if (stmt.getOperation() == MongoDbStatement.MongoOperation.COMMAND) { + return MongoDbCommandExecutor.executeCommand(this, + dbContextFuture, + statementFuture, + queryFuture); + } else { + return Multi.error(new UnsupportedOperationException( + String.format("Operation %s is not supported by query", stmt.getOperation().toString()))); + } + } + // we reach here only for query statements + + // final variable required for lambda + MongoDbStatement.MongoStatement usedStatement = stmt; + return Single.create(dbContextFuture) + .flatMap(it -> callStatement(usedStatement, statementFuture, queryFuture)); } + private MongoStatement queryOrCommand(String statement) { + try { + return new MongoDbStatement.MongoStatement(DbStatementType.QUERY, READER_FACTORY, statement); + } catch (IllegalStateException e) { + // maybe this is a command? + try { + return new MongoDbStatement.MongoStatement(DbStatementType.COMMAND, READER_FACTORY, statement); + } catch (IllegalStateException ignored) { + // we want to report the original exception + throw e; + } + } + } + + private Multi callStatement(MongoDbStatement.MongoStatement mongoStmt, + CompletableFuture statementFuture, + CompletableFuture queryFuture) { + + final MongoCollection mc = db() + .getCollection(mongoStmt.getCollection()); + final Document query = mongoStmt.getQuery(); + final Document projection = mongoStmt.getProjection(); + LOGGER.fine(() -> String.format( + "Query: %s, Projection: %s", query.toString(), (projection != null ? projection : "N/A"))); + FindPublisher publisher = noTx() + ? mc.find(query) + : mc.find(txManager().tx(), query); + if (projection != null) { + publisher = publisher.projection(projection); + } + + return Multi.create(new MongoDbRows<>(clientContext(), + publisher, + this, + DbRow.class, + statementFuture, + queryFuture) + .publisher()); + } } diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbTransaction.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbTransaction.java index 1c83ee8b44c..f4e26980cd9 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbTransaction.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbTransaction.java @@ -22,14 +22,11 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Logger; -import io.helidon.common.mapper.MapperManager; -import io.helidon.dbclient.DbMapperManager; import io.helidon.dbclient.DbStatementDml; import io.helidon.dbclient.DbStatementGet; import io.helidon.dbclient.DbStatementQuery; -import io.helidon.dbclient.DbStatements; import io.helidon.dbclient.DbTransaction; -import io.helidon.dbclient.common.InterceptorSupport; +import io.helidon.dbclient.common.DbClientContext; import com.mongodb.reactivestreams.client.ClientSession; import com.mongodb.reactivestreams.client.MongoDatabase; @@ -184,22 +181,14 @@ void addStatement(MongoDbStatement stmt) { * * @param db MongoDB database * @param tx MongoDB client session (transaction handler) - * @param statements configured statements to be used by database provider - * @param dbMapperManager mapper manager of all configured DB mappers - * @param mapperManager mapper manager of all configured mappers - * @param interceptors interceptors to be executed + * @param clientContext client context */ - MongoDbTransaction( - MongoDatabase db, - ClientSession tx, - DbStatements statements, - DbMapperManager dbMapperManager, - MapperManager mapperManager, - InterceptorSupport interceptors - ) { - super(db, statements, dbMapperManager, mapperManager, interceptors); + MongoDbTransaction(MongoDatabase db, + ClientSession tx, + DbClientContext clientContext) { + super(db, clientContext); this.txManager = new TransactionManager(tx); - } + } @Override public DbStatementQuery createNamedQuery(String statementName, String statement) { @@ -233,7 +222,7 @@ public DbStatementDml createNamedDelete(String statementName, String statement) @Override public void rollback() { - this.txManager.rollbackOnly(); + this.txManager.rollbackOnly(); } TransactionManager txManager() { diff --git a/dbclient/tracing/pom.xml b/dbclient/tracing/pom.xml index 9c9a05bb6fa..bc8fe8667ec 100644 --- a/dbclient/tracing/pom.xml +++ b/dbclient/tracing/pom.xml @@ -47,5 +47,9 @@ io.opentracing opentracing-util + + io.helidon.dbclient + helidon-dbclient-common + diff --git a/dbclient/tracing/src/main/java/io/helidon/dbclient/tracing/DbClientTracing.java b/dbclient/tracing/src/main/java/io/helidon/dbclient/tracing/DbClientTracing.java index ed42516ae51..d36377af235 100644 --- a/dbclient/tracing/src/main/java/io/helidon/dbclient/tracing/DbClientTracing.java +++ b/dbclient/tracing/src/main/java/io/helidon/dbclient/tracing/DbClientTracing.java @@ -17,14 +17,14 @@ package io.helidon.dbclient.tracing; import java.util.Map; -import java.util.concurrent.CompletableFuture; import io.helidon.common.HelidonFeatures; import io.helidon.common.HelidonFlavor; import io.helidon.common.context.Context; +import io.helidon.common.reactive.Single; import io.helidon.config.Config; -import io.helidon.dbclient.DbInterceptor; -import io.helidon.dbclient.DbInterceptorContext; +import io.helidon.dbclient.DbClientServiceContext; +import io.helidon.dbclient.common.DbClientServiceBase; import io.helidon.tracing.config.SpanTracingConfig; import io.helidon.tracing.config.TracingConfigUtil; @@ -38,19 +38,23 @@ * Tracing interceptor. * This interceptor is added through Java Service loader. */ -public class DbClientTracing implements DbInterceptor { +public class DbClientTracing extends DbClientServiceBase { static { HelidonFeatures.register(HelidonFlavor.SE, "DbClient", "Tracing"); } + private DbClientTracing(Builder builder) { + super(builder); + } + /** * Create a new tracing interceptor based on the configuration. * - * @param config configuration node for this interceptor (currently ignored) + * @param config configuration node for this interceptor * @return a new tracing interceptor */ public static DbClientTracing create(Config config) { - return create(); + return builder().config(config).build(); } /** @@ -58,42 +62,51 @@ public static DbClientTracing create(Config config) { * @return a new tracing interceptor */ public static DbClientTracing create() { - return new DbClientTracing(); + return builder().build(); + } + + /** + * Create a new builder. + * + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); } @Override - public CompletableFuture statement(DbInterceptorContext interceptorContext) { + protected Single apply(DbClientServiceContext serviceContext) { SpanTracingConfig spanConfig = TracingConfigUtil.spanConfig("dbclient", "statement"); if (!spanConfig.enabled()) { - return CompletableFuture.completedFuture(interceptorContext); + return Single.just(serviceContext); } - Context context = interceptorContext.context(); + Context context = serviceContext.context(); Tracer tracer = context.get(Tracer.class).orElseGet(GlobalTracer::get); // now if span context is missing, we build a span without a parent - Tracer.SpanBuilder spanBuilder = tracer.buildSpan(interceptorContext.statementName()); + Tracer.SpanBuilder spanBuilder = tracer.buildSpan(serviceContext.statementName()); context.get(SpanContext.class) .ifPresent(spanBuilder::asChildOf); Span span = spanBuilder.start(); - span.setTag("db.operation", interceptorContext.statementType().toString()); + span.setTag("db.operation", serviceContext.statementType().toString()); if (spanConfig.logEnabled("statement", true)) { - Tags.DB_STATEMENT.set(span, interceptorContext.statement()); + Tags.DB_STATEMENT.set(span, serviceContext.statement()); } Tags.COMPONENT.set(span, "dbclient"); - Tags.DB_TYPE.set(span, interceptorContext.dbType()); + Tags.DB_TYPE.set(span, serviceContext.dbType()); - interceptorContext.statementFuture().thenAccept(nothing -> { + serviceContext.statementFuture().thenAccept(nothing -> { if (spanConfig.logEnabled("statement-finish", true)) { span.log(Map.of("type", "statement")); } }); - interceptorContext.resultFuture().thenAccept(count -> { + serviceContext.resultFuture().thenAccept(count -> { if (spanConfig.logEnabled("result-finish", true)) { span.log(Map.of("type", "result", "count", count)); @@ -109,6 +122,21 @@ public CompletableFuture statement(DbInterceptorContext in return null; }); - return CompletableFuture.completedFuture(interceptorContext); + return Single.just(serviceContext); + } + + /** + * Fluent API builder for {@link io.helidon.dbclient.tracing.DbClientTracing}. + */ + public static class Builder extends DbClientServiceBuilderBase + implements io.helidon.common.Builder { + + private Builder() { + } + + @Override + public DbClientTracing build() { + return new DbClientTracing(this); + } } } diff --git a/dbclient/tracing/src/main/java/io/helidon/dbclient/tracing/DbClientTracingProvider.java b/dbclient/tracing/src/main/java/io/helidon/dbclient/tracing/DbClientTracingProvider.java index 59dfd60ca83..0eb99814f2b 100644 --- a/dbclient/tracing/src/main/java/io/helidon/dbclient/tracing/DbClientTracingProvider.java +++ b/dbclient/tracing/src/main/java/io/helidon/dbclient/tracing/DbClientTracingProvider.java @@ -15,21 +15,43 @@ */ package io.helidon.dbclient.tracing; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Logger; + import io.helidon.config.Config; -import io.helidon.dbclient.DbInterceptor; -import io.helidon.dbclient.spi.DbInterceptorProvider; +import io.helidon.dbclient.DbClientService; +import io.helidon.dbclient.spi.DbClientServiceProvider; /** * Provider of tracing interceptors. */ -public class DbClientTracingProvider implements DbInterceptorProvider { +public class DbClientTracingProvider implements DbClientServiceProvider { + private static final Logger LOGGER = Logger.getLogger(DbClientTracingProvider.class.getName()); + @Override public String configKey() { return "tracing"; } @Override - public DbInterceptor create(Config config) { + public Collection create(Config config) { + List tracingConfigs = config.asNodeList().orElseGet(List::of); + List result = new LinkedList<>(); + + for (Config tracingConfig : tracingConfigs) { + result.add(fromConfig(tracingConfig)); + } + + if (result.isEmpty()) { + LOGGER.info("DB Client tracing is enabled, yet none is configured in config."); + } + + return result; + } + + private DbClientService fromConfig(Config config) { return DbClientTracing.create(config); } } diff --git a/dbclient/tracing/src/main/java/module-info.java b/dbclient/tracing/src/main/java/module-info.java index 4163e446ed8..994e1f91a78 100644 --- a/dbclient/tracing/src/main/java/module-info.java +++ b/dbclient/tracing/src/main/java/module-info.java @@ -14,6 +14,8 @@ * limitations under the License. */ +import io.helidon.dbclient.spi.DbClientServiceProvider; + /** * Helidon DB Client Tracing. */ @@ -25,8 +27,9 @@ requires io.opentracing.api; requires io.opentracing.util; + requires io.helidon.dbclient.common; exports io.helidon.dbclient.tracing; - provides io.helidon.dbclient.spi.DbInterceptorProvider with io.helidon.dbclient.tracing.DbClientTracingProvider; + provides DbClientServiceProvider with io.helidon.dbclient.tracing.DbClientTracingProvider; } diff --git a/dbclient/tracing/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbInterceptorProvider b/dbclient/tracing/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbClientServiceProvider similarity index 88% rename from dbclient/tracing/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbInterceptorProvider rename to dbclient/tracing/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbClientServiceProvider index c97f854a3a2..723023b8663 100644 --- a/dbclient/tracing/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbInterceptorProvider +++ b/dbclient/tracing/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbClientServiceProvider @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2019, 2020 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/dependencies/pom.xml b/dependencies/pom.xml index e8b05d5d9fe..af458625f6d 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -73,7 +73,7 @@ 7.6.0.Final 1.0.0.Final 3.1.0 - 2.30.1 + 2.31 1.0.2 1.1.6 1.1.6 @@ -128,6 +128,21 @@ org.glassfish.jersey.core jersey-server ${version.lib.jersey} + + + org.glassfish.hk2.external + jakarta.inject + + + org.glassfish.jersey.media + jersey-media-jaxb + + + + + org.glassfish.jersey.core + jersey-client + ${version.lib.jersey} org.glassfish.hk2.external @@ -648,6 +663,20 @@ org.eclipse.microprofile.reactive.messaging microprofile-reactive-messaging-api ${version.lib.microprofile-reactive-messaging-api} + + + org.eclipse.microprofile.reactive-streams-operators + microprofile-reactive-streams-operators-api + + + org.eclipse.microprofile.reactive-streams-operators + microprofile-reactive-streams-operators-core + + + javax.enterprise + cdi-api + + com.netflix.hystrix @@ -936,6 +965,10 @@ jakarta.json jakarta.json-api + + com.sun.activation + jakarta.activation + diff --git a/docs-internal/context.md b/docs-internal/context.md index e08a16152da..5748300c2b3 100644 --- a/docs-internal/context.md +++ b/docs-internal/context.md @@ -85,6 +85,6 @@ service.submit(() -> {...}); Using the context (`HelidonDb`): ```java Executors.context() - .ifPresent(context -> interceptors + .ifPresent(context -> services .forEach(interceptor -> interceptor.statement(context, statementName, statement, statementFuture))); ``` \ No newline at end of file diff --git a/docs/about/01_overview.adoc b/docs/about/01_overview.adoc index 13d402323b4..670983c707c 100644 --- a/docs/about/01_overview.adoc +++ b/docs/about/01_overview.adoc @@ -39,6 +39,8 @@ Microprofile implementation. -- ==== + + == Get going [PILLARS] diff --git a/docs/about/02_introduction.adoc b/docs/about/02_introduction.adoc index d6cfac4e7a9..15dc2a41b15 100644 --- a/docs/about/02_introduction.adoc +++ b/docs/about/02_introduction.adoc @@ -17,19 +17,20 @@ /////////////////////////////////////////////////////////////////////////////// = About Helidon +:pagename: Helidon-introduction :description: about Helidon :keywords: helidon, java, microservices, microprofile -== A collection of Java libraries -Helidon provides an open source, lightweight, fast, reactive, cloud native framework for developing Java microservices. Helidon implements and supports MicroProfile, a baseline platform definition that leverages Java EE and Jakarta EE technologies for microservices and delivers application portability across multiple runtimes. +Helidon is a collection of Java libraries for writing microservices that run on a fast web core powered by Netty. Its available in two frameworks: Helidon SE and Helidon MP. + +== A Collection of Java Libraries + +Helidon provides an open source, lightweight, fast, reactive, cloud native framework for developing Java microservices. Helidon implements and supports MicroProfile, a baseline platform definition that leverages Java EE and Jakarta EE technologies for microservices and delivers application portability across multiple runtimes. -NOTE: Helidon is a collection of Java libraries for writing microservices that run on a fast web core powered by Netty. Your microservices can be plain Java applications. You do not need an - application server. - -== Cloud-native Java +== Using Cloud-native Tools with Helidon Helidon libraries interoperate with popular tools from the cloud-native space, so there's no need for any specific tooling or deployment model. Helidon can be used with: @@ -46,63 +47,65 @@ TIP: The < == Understanding the Helidon Frameworks Helidon supports two programming models for writing microservices: *Helidon SE* and *Helidon MP*. -SE is designed to be a microframework that supports the reactive programming model while Helidon MP is an Eclipse MicroProfile runtime that allows the Jakarta EE community to run microservices in a portable way. The table below shows to primary differences between Helidon SE and Helidon MP. +SE is designed to be a microframework that supports the reactive programming model, while Helidon MP is an Eclipse MicroProfile runtime that allows the Jakarta EE community to run microservices in a portable way. + +The table below shows to primary differences between Helidon SE and Helidon MP. -[width="100%",options="header"] +[cols="3,3"] |==================== -| Helidon SE | Helidon MP +| *Helidon SE* | *Helidon MP* + |Helidon SE gives you full transparency and puts you in control.|Helidon MP is built on top of the Helidon libraries and provides platform definition that is familiar to enterprise Java developers. |Microframework model with a very small footprint and limited functionality (~7 MB) | https://projects.eclipse.org/proposals/eclipse-microprofile[Eclipse MicroProfile] implementation; slightly larger footprint than SE (~13 MB) |Functional style is reactive non-blocking |Declarative style with dependency injection |Transparent "no magic" development experience; pure java application development with no annotations and no dependency injections |Jakarta EE microprofile development experience; all Jakarta components (CDI, JAX-RS, JSON-P/B) -|Learn more about http://url[Helidon SE]. | Learn more about http://url[Helidon MP]. +|Learn more about <>. | Learn more about <>. |==================== == What's New in Helidon 2.0 -The Helidon 2.0 release contains significant new features, enhancements and fixes. It also contains backward incompatible changes as described below: +The Helidon 2.0 release contains significant new features, enhancements and fixes. -* *GraalVM native-image support in Helidon MP* + -Helidon SE already supports GraalVM, but in 2.0 GraalVM native image support will also be available in Helidon MP. + -<> +TIP: For a complete list of fixes and enhancements, see the Helidon 2.0 https://github.com/oracle/helidon/blob/2.0.0/CHANGELOG.md[changelog]. -* *Helidon command line tool* + -The new CLI will support live reloading. + -<> +* *GraalVM Native-image Support in Helidon MP* + +Helidon SE already supports GraalVM, but in 2.0 GraalVM native image support will also be available in Helidon MP. <> + -* *New database client for Helidon SE* + -This will include support for the MongoDB reactive driver and brings Health Checks, Metrics, and Tracing support to every Helidon API. + -<> +* *Helidon Command Line Tool* + +One of the new features in Helidon 2.0 is the addition of a command line interface. The Helidon CLI enables developers to get started with Helidon with minimal effort: you can create a new application, build it, run it, and more, by writing some simple commands. <>. + + +* *DB Client for Helidon SE* + +The new database client for Helidon SE will include support for the MongoDB reactive driver and brings Health Checks, Metrics, and Tracing support to every Helidon API. <>. * *Extending MicroProfile Reactive Messaging and Reactive Operators Support* + -MP Reactive Operators will be included in both frameworks, while MP Reactive Messaging will only be included in Helidon MP. + -<> +MP Reactive Operators will be included in both frameworks, while MP Reactive Messaging will only be included in Helidon MP. <> and <>. * *Helidon Web Client* + -The new reactive web client can integrate with other Helidon SE APIs. + -<> - +The new reactive web client can integrate with other Helidon SE APIs. +<>. -* *Additional Websocket support* + -Based upon the Tyrus implementation, Helidon receives WebSocket API support. + -http://url[need link] +* *Additional Websocket Support* + +Based upon the Tyrus implementation, Helidon receives WebSocket API support. +<>. * *Support for Java 11 APIs* + -Helidon will require Java 11 or newer. + -<> +Helidon will require Java 11 or newer. +<>. * *CORS support for MP and SE* + -Although it is possible for any Helidon application to implement its own support for CORS, there are common tasks (such as processing preflight requests) that can be provided in a Helidon module. + -http://url[Cross-Origin Resource Sharing (CORS) in Helidon] +Although it is possible for any Helidon application to implement its own support for CORS, there are common tasks (such as processing preflight requests) that can be provided in a Helidon module. <>. - -* **Backward incompatible changes** + +* *Backward Incompatible Changes* + View the https://github.com/oracle/helidon/blob/2.0.0-M1/CHANGELOG.md#backward-incompatible-changes[changelog] for information about potential breaking changes, including package name changes. +== Next Steps + +<> diff --git a/docs/common/guides/migration.adoc b/docs/common/guides/migration.adoc new file mode 100644 index 00000000000..f4fe0ed37b4 --- /dev/null +++ b/docs/common/guides/migration.adoc @@ -0,0 +1,65 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2020 Oracle and/or 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. + +/////////////////////////////////////////////////////////////////////////////// + + +== Java 8 Runtime + +Java 8 is no longer supported. Java 11 or newer is required. + +== Common Utilities + +Since Helidon 2.x now requires Java 11 the helper classes that were provided for Java 8 +support have been removed. These have been replaced by the standard JDK classes: + +|=== +|Removed | Replacement + +|`io.helidon.reactive.Flow`|`java.util.concurrent.Flow` +|`io.helidon.common.CollectionsHelper`|Factory methods of `Set`, `Map` and `List` +|`io.helidon.common.OptionalHelper`|Methods of `java.util.Optional` +|`io.helidon.common.StackWalker`|`java.lang.StackWalker` +|`io.helidon.common.InputStreamHelper`|Methods of `java.io.InputStream` +|=== + +== Tracing + +We have upgraded to OpenTracing version 0.33.0 that is not backward compatible. OpenTracing +introduced the following breaking changes: + +|=== +|Removed | Replacement + + +|`ScopeManager.active()`|`Tracer.activeSpan()` +|`ScopeManager.activate(Span, boolean)`|`ScopeManager.activate(Span)` - second parameter is now always `false` +|`SpanBuilder.startActive()`|`Tracer.activateSpan(Span)` +|`TextMapExtractAdapter` and `TextMapInjectAdapter`|`TextMapAdapter` +| Module name changed `opentracing.api`|`io.opentracing.api` (same for `noop` and `util`) +|=== + +If you use the `TracerBuilder` abstraction in Helidon and have no custom Spans, there is no +change required + +== Security: OIDC + +When the OIDC provider is configured to use cookie (default configuration) to carry authentication information, +the cookie `Same-Site` is now set to `Lax` (used to be `Strict`). This is to prevent infinite redirects, as +browsers would refuse to set the cookie on redirected requests (due to this setting). +Only in the case of the frontend host and identity host match, we leave `Strict` as the default + + diff --git a/docs/mp/cors/01_introduction.adoc b/docs/mp/cors/01_introduction.adoc index 23daab99789..ea2da5003a0 100644 --- a/docs/mp/cors/01_introduction.adoc +++ b/docs/mp/cors/01_introduction.adoc @@ -19,83 +19,36 @@ = About CORS in Helidon MP :toc: :toc-placement: preamble -:pagename: cors-mp-introduction -:description: Helidon MP CORS Support -:keywords: helidon, java, cors, mp, microprofile +:h1Prefix: MP +:pagename: intro-cors-in-mp +:description: Introduction to CORS in Helidon MP +:keywords: helidon, java, cors, mp, microprofile, cross-origin resource sharing :javadoc-base-url-api: {javadoc-base-url}io.helidon.microprofile.cors/io/helidon/microprofile/cors :helidon-tag: https://github.com/oracle/helidon/tree/{helidon-version} :quickstart-example: {helidon-tag}/examples/quickstarts/helidon-quickstart-mp :cors-spec: https://www.w3.org/TR/cors/ :helidon-mp-cors-example: {helidon-tag}/examples/microprofile/cors +:common-page-prefix-inc: ../../shared/cors/common_shared.adoc +:se-pages-prefix-inc: ../../se/cors +:se-intro-page-inc: {se-pages-prefix-inc}/01_introduction.adoc +:mp-pages-ref-prefix: mp/cors +:mp-using-cors-ref: {mp-pages-ref-prefix}/02_using-cors.adoc +:mp-cors-config-ref: {mp-pages-ref-prefix}/03_configuration-with-cors-mp.adoc +:mp-cors-builtin-services-ref: {mp-pages-ref-prefix}/04_support-in-builtin-services.adoc +:helidon-variant: MP link:{cors-spec}[Cross-origin resource sharing] (CORS) support in Helidon MP provides a flexible -mechanism that allows a Helidon MP application to control how other web applications can access its resources, even if that web application is not served from the same domain. +mechanism that allows a Helidon MP application to control how another web application can access its resources, +even if that web application is served from a different domain. -== Getting Started -You add CORS support to your application by modifying your JAX-RS resource classes. -. Decide what type of resource sharing each endpoint in your application should allow. + +== Overview +include::{common-page-prefix-inc}[tag=cors-intro] -. Add a dependency on the Helidon MP CORS component. + -+ -The <> page describes how you -should declare dependency management for Helidon applications. For CORS support, you must include -the following dependency in your project: -+ -[source,xml,subs="attributes+"] ----- - - io.helidon.microprofile - helidon-microprofile-cors - ----- -. Edit each resource class in your application. + - For each subpath in the resource that should support CORS: -.. If you do not already have an `@OPTIONS` method for the subpath, create one. -.. To each `@OPTIONS` method add a `@CrossOrigin` annotation which specifies the CORS behavior -you want for that path. +== Next Steps +To introduce CORS into your Helidon MP application, do any or all of the following: -== Understanding the `@CrossOrigin` Annotation -Using the link:{javadoc-base-url-api}/CrossOrigin.html[`@CrossOrigin`] annotation is the preferred way to configure CORS in Helidon MP. -The following is an example of this annotation: - -[source,java] ----- -@CrossOrigin(value = {"http://foo.bar", "http://bar.foo"}, - allowHeaders = {"X-foo", "X-bar"}, - allowMethods = {HttpMethod.DELETE, HttpMethod.PUT}) ----- - -This annotation allows the resource associated with it to be shared with the origins `http://foo.bar` and `http://bar.foo` -using `DELETE` or `PUT` and that requests can include the non-standard headers `X-foo` and `X-bar`. - -== Revising Your Application - -In the link:{quickstart-example}[Helidon MP Quickstart application] you can change the greeting by sending a PUT -request to the `/greet/greeting` resource. -The example below extends the Helidon MP QuickStart application (the greeting app) to: - -* Permit unrestricted sharing of the resource that returns greetings, and -* Restrict sharing of the resource that -updates the greeting so that only the origins `http://foo.com` and `http://there.com` can change the greeting. - -[source,java] ----- -@OPTIONS -@CrossOrigin() // <1> -public void options() {} - -@OPTIONS -@Path("/greeting") -@CrossOrigin(allowMethods = {"PUT"}, allowOrigins = {"http://foo.com", "http://there.com"}) // <2> -public void optionsForGreeting() {} ----- -<1> Uses the default cross-origin sharing, which permits sharing via all HTTP methods to all origins. -<2> Specifies sharing only via the `PUT` HTTP method and only to the two listed origins. - -== Next steps - -* Learn how to use configuration. You and your users can use MicroProfile configuration to override the CORS behavior set in -the application code. - -* See the Helidon CORS support in action by building and running the link:{helidon-mp-cors-example}[CORS example]. +* Modify your code using the Helidon MP CORS API. <<{mp-using-cors-ref}, Learn more.>> +* Use configuration to allow users to override the CORS settings established in your application code. <<{mp-cors-config-ref},Learn more.>> +* Update your application to include any of the built-in Helidon services that automatically +support CORS. <<{mp-cors-builtin-services-ref},Learn more.>> diff --git a/docs/mp/cors/02_configuration-with-cors-mp.adoc b/docs/mp/cors/02_configuration-with-cors-mp.adoc deleted file mode 100644 index 46c574dc868..00000000000 --- a/docs/mp/cors/02_configuration-with-cors-mp.adoc +++ /dev/null @@ -1,68 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// - - Copyright (c) 2020 Oracle and/or 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. - -/////////////////////////////////////////////////////////////////////////////// - -= Using Configuration for CORS -:toc: -:toc-placement: preamble -:pagename: cors-mp-configuration -:description: Helidon MP CORS Configuration -:keywords: helidon, java, cors, mp, microprofile, configuration -:javadoc-base-url-api: {javadoc-base-url}io.helidon.microprofile.cors/io/helidon/microprofile/cors -:helidon-tag: https://github.com/oracle/helidon/tree/{helidon-version} -:quickstart-example: {helidon-tag}/examples/quickstarts/helidon-quickstart-mp -:cors-spec: https://www.w3.org/TR/cors/ -:helidon-mp-cors-example: {helidon-tag}/examples/microprofile/cors -:cors-se: ../../se/cors -:cors-se-intro: {cors-se}/01_introduction.adoc -:cors-se-config: {cors-se}/02_configuration.adoc -:cors-config-table-src: {cors-se-intro} -:cors-mapped-config-src: {cors-se-config} -:mapped-config-top-key: cors -:cors-config-table-exclude-methods: - -You and your users can override the annotated CORS set-up using MicroProfile configuration. - -== Understanding the CORS Configuration Format [[cors-configuration-formats]] -The support in Helidon MP for CORS configuration works with the following format. - -[[config-key-table]] -include::{cors-config-table-src}[tag=cors-config-table] - -If the cross-origin configuration is disabled (`enabled` = false), then the Helidon CORS implementation ignores the cross-origin configuration entry. - -Although there are two types of CORS cross-origin configuration, for Helidon MP use -the mapped format. - -include::{cors-mapped-config-src}[tag=mapped-config] - -The following example shows how you can express similar configuration using properties-file syntax -in your applications's `META-INF/microprofile-config.properties` file. Note that the top-level config key -must be `cors`. - -[source,properties] ----- -cors.paths.0.path-prefix = /greeting -cors.paths.0.allow-origins = http://foo.com, http://there.com, http://other.com -cors.paths.0.allow-methods = PUT, DELETE -cors.paths.1.path-prefix = / -cors.paths.1.allow-methods = GET, HEAD, OPTIONS, POST ----- - -== Next Steps - -See the Helidon CORS support in action by building and running the link:{helidon-mp-cors-example}[CORS example]. diff --git a/docs/mp/cors/02_using-cors.adoc b/docs/mp/cors/02_using-cors.adoc new file mode 100644 index 00000000000..17a416e62f0 --- /dev/null +++ b/docs/mp/cors/02_using-cors.adoc @@ -0,0 +1,123 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2020 Oracle and/or 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. + +/////////////////////////////////////////////////////////////////////////////// + += Using the Helidon MP CORS API +:toc: +:toc-placement: preamble +:h1Prefix: MP +:pagename: using-cors-in-mp +:description: Using the Helidon MP CORS API +:keywords: helidon, java, cors, mp, microprofile +:javadoc-base-url-api: {javadoc-base-url}io.helidon.microprofile.cors/io/helidon/microprofile/cors +:helidon-tag: https://github.com/oracle/helidon/tree/{helidon-version} +:quickstart-example: {helidon-tag}/examples/quickstarts/helidon-quickstart-mp +:cors-spec: https://www.w3.org/TR/cors/ +:helidon-mp-cors-example: {helidon-tag}/examples/microprofile/cors +:mp-pages-ref-prefix: mp/cors +:mp-cors-config-ref: {mp-pages-ref-prefix}/03_configuration-with-cors-mp.adoc +:helidon-variant: MP +:common-page-prefix-inc: ../../shared/cors/common_shared.adoc + +To enable CORS behavior for a resource in your Helidon MP application, you simply add the Helidon MP `@CrossOrigin` +annotation to a particular method in your resource class. + +== Understanding the `@CrossOrigin` Annotation +You set up CORS in Helidon MP using the link:{javadoc-base-url-api}/CrossOrigin.html[`@CrossOrigin`] annotation. + +The following example of the `@CrossOrigin` annotation allows the resource associated with it to be shared with the origins `\http://foo.bar` and `\http://bar.foo` +using `DELETE` or `PUT`, and permits requests to include the non-standard headers `X-foo` and `X-bar`. + +[source,java] +---- +@CrossOrigin(value = {"http://foo.bar", "http://bar.foo"}, + allowHeaders = {"X-foo", "X-bar"}, + allowMethods = {HttpMethod.DELETE, HttpMethod.PUT}) +---- + + + + +== Getting Started +To add CORS support to your Helidon MP application: + +. Determine the type of cross-origin resource sharing you want to allow + for each endpoint in your application. +. {blank} ++ +-- +Add a dependency on the Helidon {helidon-variant} CORS artifact to your Maven `pom.xml` file. + +include::{common-page-prefix-inc}[tag=add-cors-dependency] +// tag::actual-cors-dependency[] +[source,xml,subs="attributes+"] +---- + + io.helidon.microprofile + helidon-microprofile-cors + +---- +// end::actual-cors-dependency[] +-- +. Edit each JAX-RS resource class in your application to add the desired CORS behavior as described in the following sections. + + + +== Adding CORS Support to Your Helidon MP Application +Adding CORS behavior to your Helidon MP application involves two simple changes to your application code: + +. For each resource and subresource in each resource class, make sure you have a Java method annotated with +`@OPTIONS` and with the correct `@Path`. Create these methods for each resource if you do not already have them. +. To each of those methods, add a `@CrossOrigin` annotation that describes the cross-origin sharing you want +for that resource. + +The Helidon MP CORS implementation automatically uses the `@CrossOrigin` annotation you add to each `@OPTIONS` method to +enforce cross-origin sharing behavior for the resource identified by that method's `@Path` annotation. + +For an informal look at the reasons for applying the `@CrossOrigin` annotation to the `@OPTIONS` method, instead of another +method, see <>. + +== Sample Application Using the `@CrossOrigin` Annotation + +In the link:{quickstart-example}[Helidon MP Quickstart application] you can change the greeting by sending a `PUT` +request to the `/greet/greeting` resource. +The example below extends the Helidon MP QuickStart application (the greeting app) to: + +* Permit unrestricted sharing of the resource that returns greetings, and +* Restrict sharing of the resource that +updates the greeting so that only the origins `\http://foo.com` and `\http://there.com` can change the greeting. + +[source,java] +---- +@OPTIONS +@CrossOrigin() // <1> +public void options() {} + +@OPTIONS +@Path("/greeting") +@CrossOrigin(allowMethods = {"PUT"}, allowOrigins = {"http://foo.com", "http://there.com"}) // <2> +public void optionsForGreeting() {} +---- +<1> Uses the default cross-origin sharing, which permits sharing via all HTTP methods to all origins. +<2> Specifies sharing only via the `PUT` HTTP method and only to the two listed origins. + +== Next steps + +* Use MicroProfile configuration to override the CORS behavior set in +the application code. <<{mp-cors-config-ref},Learn more.>> + +* See the Helidon CORS support in action by building and running the link:{helidon-mp-cors-example}[CORS example]. diff --git a/docs/mp/cors/03_configuration-with-cors-mp.adoc b/docs/mp/cors/03_configuration-with-cors-mp.adoc new file mode 100644 index 00000000000..d536b812fad --- /dev/null +++ b/docs/mp/cors/03_configuration-with-cors-mp.adoc @@ -0,0 +1,96 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2020 Oracle and/or 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. + +/////////////////////////////////////////////////////////////////////////////// + += Using Configuration with CORS in Helidon MP +:toc: +:toc-placement: preamble +:h1Prefix: MP +:pagename: cors-mp-configuration +:description: Helidon MP CORS Configuration +:keywords: helidon, java, cors, mp, microprofile, configuration +:javadoc-base-url-api: {javadoc-base-url}io.helidon.microprofile.cors/io/helidon/microprofile/cors +:javadoc-base-url-webserver: {javadoc-base-url}io.helidon.webserver/io/helidon/webserver +:helidon-tag: https://github.com/oracle/helidon/tree/{helidon-version} +:quickstart-example: {helidon-tag}/examples/quickstarts/helidon-quickstart-mp +:cors-spec: https://www.w3.org/TR/cors/ +:helidon-mp-cors-example: {helidon-tag}/examples/microprofile/cors +:cors-se: ../../se/cors +:cors-se-intro: {cors-se}/01_introduction.adoc +:cors-se-api: {cors-se}/02_using-the-api.adoc +:cors-se-config: {cors-se}/03_using-configuration.adoc +:cors-config-table-src: {cors-se-api} +:cors-mapped-config-src: {cors-se-config} +:mp-pages-ref-prefix: mp/cors +:mp-cors-builtin-services-ref: {mp-pages-ref-prefix}/04_support-in-builtin-services.adoc +:mapped-config-top-key: cors +:mapped-config-id-callout: The unique identifier for this mapped CORS config section must be `cors`. +:common-page-prefix-inc: ../../shared/cors/common_shared.adoc +:config-table-methods-column-explainer: the annotation parameters +:config-table-methods-column-header: Annotation Parameter +:basic-table-intro: The table below lists the parameters for the `@CrossOriginConfig` annotation and the configuration keys that identify the CORS characteristics. +:helidon-variant: MP + +Your application code establishes the CORS behavior of your endpoints using the `@CrossOrigin` annotation. +You and your users can override that behavior, as well as the CORS behavior of the built-in services, +using MicroProfile configuration. + +include::{common-page-prefix-inc}[tag=cors-configuration-formats-intro] + +include::{common-page-prefix-inc}[tag=basic-cross-origin-config] + +=== Understanding the Mapped Cross-Origin Configuration Format + +In Helidon MP, you use +the mapped cross-origin configuration format. + +// We want to use +// include::{cors-mapped-config-src}[tag=mapped-config] +// and assign an attribute for the callout 1 text. But because the MP value uses backticks for monospaced +// text, we'd have to be a little clever in the original callout. And that cleverness works when +// the document is rendered in our editors, and even when using asciidoctor to HTML, but not +// to our site. +include::{common-page-prefix-inc}[tag=mapped-config-prefix] +<1> The unique identifier for this mapped CORS config section must be `cors`. +include::{common-page-prefix-inc}[tag=mapped-config-suffix] + +== Specifying Override Values in Configuration +In configuration, you can specify the same CORS-related attributes that you specify using the `@CrossOrigin` annotation. + +The following example shows how you can express configuration similar to that shown +previously using the mapped cross-origin configuration format. +Here, the example uses properties-file syntax +in your applications's `META-INF/microprofile-config.properties` file. Note that the top-level config key +must be `cors`. + +[source,properties] +---- +cors.paths.0.path-pattern = /greeting +cors.paths.0.allow-origins = http://foo.com, http://there.com, http://other.com +cors.paths.0.allow-methods = PUT, DELETE +cors.paths.1.path-pattern = / +cors.paths.1.allow-methods = GET, HEAD, OPTIONS, POST +---- + +NOTE: Remember that if you set configuration in a file that you include as part of your application JAR file, then you need to +rebuild and restart your application for any changes to take effect. + +== Next Steps +* Use these same configuration techniques to control the behavior of the CORS-enabled +built-in services. <<{mp-cors-builtin-services-ref},Learn more.>> + +* See the Helidon CORS support in action by building and running the link:{helidon-mp-cors-example}[CORS example]. diff --git a/docs/mp/cors/04_support-in-builtin-services.adoc b/docs/mp/cors/04_support-in-builtin-services.adoc new file mode 100644 index 00000000000..39842182101 --- /dev/null +++ b/docs/mp/cors/04_support-in-builtin-services.adoc @@ -0,0 +1,89 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2020 Oracle and/or 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. + +/////////////////////////////////////////////////////////////////////////////// + += Using CORS in Helidon MP Built-in Services +:toc: +:toc-placement: preamble +:h1Prefix: MP +:pagename: cors-built-in-service-support +:description: Helidon MP CORS Support in Built-in Services +:keywords: helidon, java, cors, mp, services +:helidon-tag: https://github.com/oracle/helidon/tree/{helidon-version} +:quickstart-example: {helidon-tag}/examples/quickstarts/helidon-quickstart-mp +:javadoc-base-url-api: {javadoc-base-url}io.helidon.microprofile.cors/io/helidon/microprofile/cors +:javadoc-base-url-webserver: {javadoc-base-url}io.helidon.webserver/io/helidon/webserver +:cors-se-intro: 01_introduction.adoc +:cors-config-table-src: {cors-se-intro} +:health-page: mp/health/01_introduction.adoc +:metrics-page: mp/metrics/01_introduction.adoc +:openapi-page: mp/openapi/01_openapi.adoc +:cors-config-table-exclude-methods: +:cors-services-is-se!: +:helidon-variant: MP +:cors-se-include: ../../se/cors +:se-built-in-services-page-include: {cors-se-include}/04_support-in-builtin-services.adoc +:cors-se-using-page: 02_using-the-api.adoc +:cors-dependency-src: ../../se/cors/{cors-se-using-page} +:cors-se-api-doc: ../../se/cors/02_using-the-api.adoc +// Following is resolved in an SE file, so the relative path must lead to the MP file. +:cors-mp-using-cors-page-inc: ../../mp/cors/02_using-cors.adoc +:mp-pages-ref-prefix: mp/cors +:mp-cors-config-ref: {mp-pages-ref-prefix}/03_configuration-with-cors-mp.adoc +:actual-cors-dependency-src: {cors-mp-using-cors-page-inc} +:se-cors-home-inc: ../../se/cors +:se-cors-dependency-page: 02_using-the-api.adoc +:dependency-inc: {se-cors-home-inc}/{se-cors-dependency-page} +:common-page-prefix-inc: ../../shared/cors/common_shared.adoc + +Several built-in Helidon services -- health, metrics, and OpenAPI -- have integrated CORS support. +You can include these services in your application and control their CORS behavior. + +include::{common-page-prefix-inc}[tag=understanding-cors-support-in-services] + +include::{common-page-prefix-inc}[tag=builtin-getting-started] + +include::{common-page-prefix-inc}[tags=configuring-cors-for-builtin-services;!se-config-example;!se-code-changes-for-builtin-services-config] + +The following example restricts sharing of + +* the `/health` resource, provided by the health built-in service, to only the origin `\http://there.com`, and +* the `/metrics` resource, provided by the metrics built-in service, to only the origin `\http://foo.com`. + +[source,hocon] +---- +... +health: + cors: + allow-origins: [http://there.com] +metrics: + cors: + allow-origins: [http://foo.com] +... +---- + +include::{common-page-prefix-inc}[tag=accessing-shared-resources-intro] +[source,bash] +---- +mvn package +java -jar target/helidon-quickstart-mp.jar +... +2020.05.12 05:44:08 INFO io.helidon.microprofile.server.ServerCdiExtension Thread[main,5,main]: Server started on http://localhost:8080 (and all other host addresses) in 5280 milliseconds (since JVM startup). +... +---- + +include::{common-page-prefix-inc}[tag=accessing-shared-resources-main] diff --git a/docs/mp/cors/hide_why-options.adoc b/docs/mp/cors/hide_why-options.adoc new file mode 100644 index 00000000000..db2f9e433f7 --- /dev/null +++ b/docs/mp/cors/hide_why-options.adoc @@ -0,0 +1,77 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2020 Oracle and/or 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. + +/////////////////////////////////////////////////////////////////////////////// + += Why `@OPTIONS`? +:toc: +:toc-placement: preamble +:h1Prefix: MP +:pagename: using-cors-in-mp-why-options +:description: Exploration of why Helidon MP associates the `@CrossOrigin` annotation with `@OPTIONS` methods. +:keywords: helidon, java, cors, mp, microprofile, jax-rs cross-origin resource sharing + +There are some good reasons why it is `@OPTIONS` methods that you decorate with the Helidon MP +`@CrossOrigin` annotation. Take an informal look at the rationale for this choice. + +== The Resource +At the heart of cross-origin resource sharing is the _resource_ itself. +CORS lets you control how a given resource should be shared among various origins. +All the attributes of CORS -- whether authentication should be used, what headers can be passed through on +CORS-controlled requests, and so on -- pertain to a given resource. + +In Helidon MP, the parameters defined on the `@CrossOrigin` annotation map directly to those +CORS sharing attributes. +It would be natural, then, to use `@CrossOrigin` to annotate the single Java element in the application that represents +a resource. + +== Methods, Resources, and Subresources in JAX-RS Resource Classes +Unfortunately, there is no single Java element that is sure to correspond one-to-one with a JAX-RS resource, +for two reasons. + +. JAX-RS allows a resource class to define one or more subresources, denoted by the `@Path` annotation +on methods. So a resource class does not necessarily represent only a single resource. +. A JAX-RS resource class can contain multiple endpoints for the same resource. +A common example is two methods, annotated with `@GET` and `@PUT` respectively, that have the same path. + +Although no single endpoint method by itself fully represents the resource, at +least each endpoint method maps to exactly one resource. +So we could annotate any one of those endpoint methods with `@CrossOrigin` and unambiguously link +the CORS behavior that the annotation defines to the resource. + +But which endpoint method, and why? + +== `OPTIONS` in CORS, `@OPTIONS` in JAX-RS, and Technical Reality +The `OPTIONS` HTTP method plays an important role in CORS. +While the CORS protocol _applies_ to all HTTP methods, it _relies on_ `OPTIONS` -- with suitable headers -- +to represent CORS pre-flight requests. +From that point of view, the `OPTIONS` HTTP method has a more prominent place in CORS than the other methods. + +In a JAX-RS resource class, the `@OPTIONS` annotation denotes which endpoint method should receive incoming `OPTIONS` +HTTP requests for a resource. +Therefore, we could view a Java method annotated with `@OPTIONS` as somewhat distinguished in the same way that +we think of the `OPTIONS` HTTP method as distinguished within the CORS protocol. + +Furthermore, there is this technical detail: +Helidon MP uses a JAX-RS filter internally to gather information about each `@CrossOrigin` annotation. +Some JAX-RS implementations do not provide the filter with what it needs to find and introspect the `@CrossOrigin` +annotation unless the application itself implements the `@OPTIONS` endpoint for the resource. + +== The Bottom Line +If you want a resource to participate in CORS, Helidon MP needs you to implement the `@OPTIONS` endpoint method for the +resource, even if the method does nothing. +Given that you have to write that method, and given that any endpoint method uniquely identifies its resource, +the `@OPTIONS` method is a reasonable place to ask you to annotate with `@CrossOrigin`. diff --git a/docs/mp/grpc/01_mp_server_side_services.adoc b/docs/mp/grpc/01_mp_server_side_services.adoc index abdddefae2e..f75fbf2ecbc 100644 --- a/docs/mp/grpc/01_mp_server_side_services.adoc +++ b/docs/mp/grpc/01_mp_server_side_services.adoc @@ -54,7 +54,7 @@ For example: .Simple gRPC Service ---- @ApplicationScoped -@io.helidon.microprofile.grpc.core.RpcService +@io.helidon.microprofile.grpc.core.Grpc public class StringService { @io.helidon.microprofile.grpc.core.Unary @@ -65,7 +65,7 @@ public class StringService { ---- The code above is a simple service with a single unary method that just converts a String to uppercase. -The important parts in the example are the `@ApplicationScoped`, `@RpcService` and `@Unary` annotations; these, +The important parts in the example are the `@ApplicationScoped`, `@Grpc` and `@Unary` annotations; these, along with other annotations discussed later, allow the gRPC MP APIs to discover, configure and deploy the service. Of course Helidon gRPC MP does not preclude you from using the Protobuf files approach, traditional gRPC Java services @@ -77,22 +77,22 @@ annotations. [source,java] ---- @ApplicationScoped <1> -@io.helidon.microprofile.grpc.core.RpcService <2> +@io.helidon.microprofile.grpc.core.Grpc <2> public class StringService { ---- <1> The `ApplicationScoped` annotation is what makes the service implementation a CDI bean and hence discoverable. -<2> The `RpcService` annotation is what defines the class as a gRPC service so that when the bean is iscovered it is +<2> The `Grpc` annotation is what defines the class as a gRPC service so that when the bean is discovered it is then deployed by the gRPC MP server. === Service Name -By default when a class is annotated with `RpcService` the class name will be used as the gRPC service name. So in the example +By default when a class is annotated with `Grpc` the class name will be used as the gRPC service name. So in the example above the service name will be `StringService`. This can be change by supplying a name to the annotation. [source,java] ---- @ApplicationScoped -@io.helidon.microprofile.grpc.core.RpcService(name="Strings") <1> +@io.helidon.microprofile.grpc.core.Grpc(name="Strings") <1> public class StringService { ---- <1> in the example above the name of the deployed service will be `Strings`. diff --git a/docs/mp/grpc/02_mp_clients.adoc b/docs/mp/grpc/02_mp_clients.adoc index 6da73f7a5d1..466017fe461 100644 --- a/docs/mp/grpc/02_mp_clients.adoc +++ b/docs/mp/grpc/02_mp_clients.adoc @@ -61,7 +61,7 @@ For example, suppose we have a simple server side service that has a unary metho .Simple gRPC Service ---- @ApplicationScoped -@io.helidon.microprofile.grpc.core.RpcService +@io.helidon.microprofile.grpc.core.Grpc public class StringService { @io.helidon.microprofile.grpc.core.Unary @@ -81,7 +81,7 @@ To write a client for the StringService all that is required is an interface. .Simple gRPC Service ---- @ApplicationScoped -@io.helidon.microprofile.grpc.core.RpcService +@io.helidon.microprofile.grpc.core.Grpc public class StringService { @io.helidon.microprofile.grpc.core.Unary @@ -100,7 +100,7 @@ unary method signature: .Simple gRPC Service ---- @ApplicationScoped -@io.helidon.microprofile.grpc.core.RpcService +@io.helidon.microprofile.grpc.core.Grpc public class StringService { @io.helidon.microprofile.grpc.core.Unary @@ -114,7 +114,7 @@ We could also have made the client asynchronous by using one of the async method .Simple gRPC Service ---- @ApplicationScoped -@io.helidon.microprofile.grpc.core.RpcService +@io.helidon.microprofile.grpc.core.Grpc public class StringService { @io.helidon.microprofile.grpc.core.Unary @@ -237,13 +237,13 @@ The field is then annotated so that CDI will inject the client proxy into the fi public class Client { @Inject <1> - @GrpcServiceProxy <2> + @GrpcProxy <2> @GrpcChannel(name = "test-server") <3> private StringService stringService; ---- <1> The `@Inject` annotation tells the CDI to inject the client implementation; the gRPC MP APIs have a bean provider that does this. -<2> The `@GrpcServiceProxy` annotation is used by the CDI container to match the injection point to the gRPC MP APIs provider +<2> The `@GrpcProxy` annotation is used by the CDI container to match the injection point to the gRPC MP APIs provider <3> The `@GrpcChannel` annotation identifies the gRPC channel to be used by the client. The name used in the annotation refers to a channel name in the application configuration. diff --git a/docs/mp/guides/15_migration.adoc b/docs/mp/guides/15_migration.adoc new file mode 100644 index 00000000000..f26d2106742 --- /dev/null +++ b/docs/mp/guides/15_migration.adoc @@ -0,0 +1,119 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2020 Oracle and/or its affiliates. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +/////////////////////////////////////////////////////////////////////////////// + += Helidon MP Migration Guide +:description: Helidon MP Migration Guide +:keywords: helidon, porting, migration, incompatibilities +:helidon-uc-flavor: MP +:helidon-lc-flavor: mp + +In Helidon 2 we have made some changes to APIs and runtime behavior. This guide +will help you migrate a Helidon MP 1.x application to 2.x. + +include::../../common/guides/migration.adoc[] + +== MicroProfile Bundles + +We have removed the versioned MicroProfile bundles (i.e. `helidon-microprofile-x.x`), and introduced +unversioned core and full bundles: + +- `io.helidon.microprofile.bundles:helidon-microprofile-core` - contains only MP Server + and Config. Allows you to add only the specifications needed by your application. +- `io.helidon.microprofile.bundles:helidon-microprofile` - contains the latest full + MicroProfile version implemented by Helidon + +== Application Main and Startup + +* `io.helidon.microprofile.server.Main` has been deprecated. Use `io.helidon.microprofile.cdi.Main` instead. +* `io.helidon.microprofile.server.Server` is still available, although the features are much reduced. +* You no longer need to initialize Java Util Logging explicitly. Put `logging.properties` on the classpath + or in the current directory to be automatically picked up to configure Java Util Logging. + +== JAX-RS Applications + +Helidon 1.x usually required that you have an `Application` subclass that returned +the Application classes to scan. For common cases this is no longer necessary, and you +might be able to remove your `Application` class. + +JAX-RS applications now work similarly to how they work in application servers: + +* if there is an `Application` subclass that returns anything from `getClasses` or `getSingletons`, it is used as is +* if there is an `Application` subclass that returns empty sets from these methods, all available resource classes will be part of such an application +* if there is no `Application` subclass, a synthetic application will be created with all available resource classes +* `Application` subclasses MUST be annotated with `@ApplicationScoped`, otherwise they are ignored + +== MicroProfile JWT-Auth + +If a JAX-RS application exists that is annotated with `@LoginConfig` with value MP-JWT, +the correct authentication provider is added to security. The startup would fail if the provider is required yet not configured. + +== Security in Helidon MP + +* If there is no authentication provider configured, authentication will now fail. +* If there is no authorization provider configured, the ABAC provider will be configured. + +In Helidon 1.x these were configured if there was no provider configured overall. + +== CDI and MicroProfile Server + +In order to support GraalVM `native-image` we have had to re-implement how CDI is initialized +and started. This has resulted in some changes in APIs and behavior: + +* You can no longer start the CDI container yourself. +* You can only run a single instance of Server in a JVM. +* If you use `SeContainerInitializer` you will get an exception. + ** This can be worked around by configuration property `mp.initializer.allow=true`, and warning can + be removed using `mp.initializer.no-warn=true` + ** Once `SeContainerInitializer` is used you can no longer use MP with `native-image` +* You can no longer provide a `Context` instance. The root context is now built-in. +* `MpService` and `MpServiceContext` have been removed. + ** Methods from context have been moved to `JaxRsCdiExtension` and `ServerCdiExtension`. + These can be accessed from CDI extension through `BeanManager.getExtension`. + ** Methods `register` can be used on current `io.helidon.context.Context` + ** `MpService` equivalent is a CDI extension. All Helidon services were refactored to + CDI extension (you can use these for reference). +* `Server.cdiContainer` is removed, use `CDI.current()` instead. + + +== Metrics + +Helidon now supports only MicroProfile Metrics 2.x. Support for Metrics 1.x has been removed, +and modules for 2.x have been renamed from `metrics2` to `metrics`. + +== Java EE dependencies +We have moved from dependencies in groupId `javax` (Java EE modules) to dependencies +in groupId `jakarta` (Jakarta EE modules). + +In case you declared a dependency on a javax module, you should change it to a jakarta one. + +Example: +```xml + + javax.activation + javax.activation-api + +``` + +should be changed to +```xml + + jakarta.activation + jakarta.activation-api + +``` +As the `javax` module is no longer in dependency management of Helidon parent pom files. \ No newline at end of file diff --git a/docs/mp/introduction/01_introduction.adoc b/docs/mp/introduction/01_introduction.adoc index ddbd770e962..2ffa20aebe6 100644 --- a/docs/mp/introduction/01_introduction.adoc +++ b/docs/mp/introduction/01_introduction.adoc @@ -16,65 +16,87 @@ /////////////////////////////////////////////////////////////////////////////// -= About Helidon MP += Helidon MP Introduction :description: about Helidon MP :keywords: helidon, java, microservices, microprofile +:h1Prefix:MP -== Helidon MP +Helidon MP is an Eclipse MicroProfile runtime that allows the Jakarta EE community to run microservices in a portable way. -Helidon is a collection of Java libraries for writing microservices. Helidon -offers two programming models: <> -and Helidon MP. -Helidon MP is an implementation of the +== About Helidon MP Components + +Helidon MP {helidon-version} is an implementation of the https://microprofile.io[MicroProfile] -https://github.com/eclipse/microprofile/releases[specification]. -Helidon {helidon-version} supports MicroProfile {mp-version}. Since -MicroProfile has its roots in Java EE, you'll find that the MicroProfile +https://github.com/eclipse/microprofile/releases[specification] and supports MicroProfile {mp-version}. Since +MicroProfile has its roots in Java EE, the MicroProfile APIs follow a familiar, declarative approach with heavy use of annotations. -This makes it a good choice for Java EE developers. +This makes it a good choice for Java EE developers. + +Helidon has added additional APIs to the core set of Microprofile APIs giving you all the capabilities you need for writing modern cloud native applications. + +[cols="2,5"] +|======= +|CDI Extensions +|In addition to MicroProfile support, Helidon MP provides CDI extensions to address areas not covered by MicroProfile.<>. + +|Configuration +|The MP Config component provides a Java API to load and process configuration properties in key/value form into a Config object which the application can use to retrieve config data. +<>. + +| CORS Support +| Although it is possible for any Helidon application to implement its own support for CORS, there are common tasks (such as processing preflight requests) that can be provided in a Helidon module. <>. + +|GraalVM Native Image Support for Helidon MP +|GraalVM native-image support is now available for both Helidon MP and Helidon SE. Helidon MP includes support for GraalVM to enable the conversion of Helidon MP applications to native executable code via the native-image utility.<>. + +| gRPC +| Helidon gRPC Server provides a framework for creating gRPC (general-purpose Remote Procedure Calls) applications. +<>. + +| Health Checks +| The health check API combines the statuses of all the dependencies that affect availability and the ability to perform correctly such as network latency and storage. <>. + + +|JAX-RS/Jersey +|Helidon MP supports building RESTful services using JAX-RS/Jersey. <>. + + +|JSON-P and JSON-B +| Helidon supports both JSON processing (JSON P) and JSON building (JSON B) APIs. + + +|Metrics +|Heldion supports both a base set and a Helidon-specific set of metrics that expose information in JSON format (as specified by the MicroProfile Metrics specification) or in plain text (for Prometheus metrics). +<>. + + +|OpenAPI +|The OpenAPI in Helidon SE allows your Helidon SE application to serve an OpenAPI document that describes your application’s endpoints. +<>. -== MicroProfile APIs +|OpenTracing +|Helidon includes support for tracing through the OpenTracing APIs. Tracing is integrated with WebServer, gRPC Server, and Security. +<>. -MicroProfile starts with a core of Java EE APIs, then defines a number -of new APIs to add capabilities you need for writing modern cloud native -applications: -* JAX-RS -* JSON-P -* JSON-B -* CDI -* MicroProfile Config -* MicroProfile Fault Tolerance -* MicroProfile Health -* MicroProfile JWT Authentication -* MicroProfile Metrics -* MicroProfile OpenAPI -* MicroProfile OpenTracing -* MicroProfile Rest Client +|Reactive Messaging and Reactive Operators +|With Helidon MP 2.0 you can now formalize manipulation with reactive streams and reactive messaging. Reactive messaging heavily depends on standardized operators so together they provide great portability between existing implementations. <> and <>. -For more information see <>. +|Security +|The MP security modules support authentication, authorization, outbound security and audits for your applications. +<>. -== Helidon MP CDI Extensions -In addition to MicroProfile support, Helidon MP provides -<> to address areas not -covered by MicroProfile. Examples include: +|======= -* DataSource for Oracle UCP and HikariCP -* JPA -* JTA -* Jedis -* OCI Object storage +//need JPA and JWT Auth info, fault tolerance -== No Application Server -Helidon is a collection of libraries that runs on top of Netty. It is not -derived from a Java EE application server. That means your cloud native -application is compact -and efficient without unnecessary overhead or bloat. +== Next Steps +Learn about the steps needed to get started with Helidon MP <>. -== Try it now +For more information about the Helidon MicroProfile APIs see https://github.com/oracle/helidon/wiki/Supported-APIs[the Helidon API Wiki page]. Try the <> to get your first Helidon MP application up and running in minutes. diff --git a/docs/mp/introduction/02_microprofile.adoc b/docs/mp/introduction/02_microprofile.adoc index 2450aefcd56..8b9fecd2140 100644 --- a/docs/mp/introduction/02_microprofile.adoc +++ b/docs/mp/introduction/02_microprofile.adoc @@ -16,29 +16,21 @@ /////////////////////////////////////////////////////////////////////////////// -= MicroProfile Introduction -:description: Helidon MicroProfile introduction += Helidon MicroProfile +:description: getting started with Helidon Microprofile :keywords: helidon, microprofile, micro-profile +:h1Prefix: MP -MicroProfile is a collection of enterprise Java APIs that should feel familiar to -Java EE developers. MicroProfile includes existing APIs such as JAX-RS, JSON-P and -CDI, and adds additional APIs in areas such as configuration, metrics, fault -tolerance and more. +Complete these tasks to get started with your MicroProfile application. == Getting Started with Helidon MicroProfile -Helidon MP {helidon-version} supports -MicroProfile {mp-version}. You can find the exact version of APIs supported on the -https://github.com/oracle/helidon/wiki/Supported-APIs[Helidon Supported APIs] -wiki page. - Helidon provides a MicroProfile server implementation (`io.helidon.microprofile.server`) that encapsulates the Helidon WebServer. You can either instantiate the server directly as is done in the <> or use its built-in `main` as shown below. -Complete these tasks to get started with your MicroProfile application. === Maven Coordinates diff --git a/docs/se/cors/01_introduction.adoc b/docs/se/cors/01_introduction.adoc index b37e8f34bbb..9726ddb0e50 100644 --- a/docs/se/cors/01_introduction.adoc +++ b/docs/se/cors/01_introduction.adoc @@ -19,6 +19,7 @@ = About CORS in Helidon SE :toc: :toc-placement: preamble +:h1Prefix: SE :pagename: cors-introduction :description: Helidon SE CORS Support :keywords: helidon, java, cors, se @@ -26,148 +27,28 @@ :quickstart-example: {helidon-tag}/examples/quickstarts/helidon-quickstart-se :cors-spec: https://www.w3.org/TR/cors/ :helidon-se-cors-example: {helidon-tag}/examples/cors -:model-reader-java: {mp-openapi-prefix}/api/src/main/java/org/eclipse/microprofile/openapi/OASModelReader.java -:filter-java: {mp-openapi-prefix}/api/src/main/java/org/eclipse/microprofile/openapi/OASFilter.java :helidon-tag: https://github.com/oracle/helidon/tree/{helidon-version} :quickstart-example: {helidon-tag}/examples/quickstarts/helidon-quickstart-se :javadoc-base-url-api: {javadoc-base-url}io.helidon.webserver.cors/io/helidon/webserver/cors :javadoc-base-url-webserver: {javadoc-base-url}io.helidon.webserver/io/helidon/webserver +:helidon-variant: SE +:common-page-prefix-inc: ../../shared/cors/common_shared.adoc -link:{cors-spec}[Cross-origin resource sharing] (CORS) support in Helidon SE provides a flexible -mechanism that allows a Helidon SE application to control how other web applications can access its resources, even if that web application is not served from the same domain. - - - -== Getting Started - -Before you begin you must: - - -. Determine the type of cross origin sharing you want to allow for each endpoint in your application. -+ -For example, suppose you want to allow unrestricted access for GET, HEAD, and POST requests -(what CORS refers to as "simple" requests), but permit other types of requests only from the two -origins `foo.com` and `there.com`. This means that you have two types of CORS sharing: relaxed for the -simple requests and stricter for others. In practice, you can use as many types of sharing as makes sense for -your application. - -. Add the CORS dependencies to the Maven `pom.xml` file. -. Add the CORS support in your application. - -The <> page describes how you -should declare dependency management for Helidon applications. For CORS support, you must include -the following dependency in your project: - -[source,xml,subs="attributes+"] ----- - - io.helidon.webserver - helidon-webserver-cors - ----- - -== Adding CORS Support in Your Helidon SE Application [[adding-cors-support]] -Because Helidon SE does not use annotation processing to identify endpoints, you need to -provide the CORS information for your application another way. - -The high-level process for adding CORS support in your Helidon SE application is: - -. Create a link:{javadoc-base-url-api}/CrossOriginConfig.html[`CrossOriginConfig`] instance for each type of resource sharing that you decided your application should provide. -The `CrossOriginConfig` Java class represents the details for a particular type of sharing. - -. Create a link:{javadoc-base-url-api}/CorsSupport.html[`CorsSupport`] instance that includes the `CrossOriginConfig` instances from the previous step. -. Use that `CorsSupport` instance to set up the routing rules. - -Each of these classes has an associated builder. -// tag::cors-config-table[] -The table below describes -ifndef::cors-config-table-exclude-methods[the methods on the `CrossOriginConfig.Builder` class and ] -the configuration keys that map to the headers defined in the CORS protocol. - -ifndef::cors-config-table-exclude-methods[] -[width="100%",options="header",cols="4*"] -endif::[] -ifdef::cors-config-table-exclude-methods[] -[width="100%",options="header",cols="3*"] -endif::[] -|==================== -ifndef::cors-config-table-exclude-methods[| Methods ] -| Configuration Key | Default | CORS Header Name - -ifndef::cors-config-table-exclude-methods[|`allowCredentials`] -|`allow-credentials`|`false`|`Access-Control-Allow-Credentials` - -ifndef::cors-config-table-exclude-methods[|`allowHeaders`] -|`allow-headers`|`["*"]`|`Access-Control-Allow-Headers` - -ifndef::cors-config-table-exclude-methods[|`allowMethods`] -|`allow-methods`|`["*"]`|`Access-Control-Allow-Methods` - -ifndef::cors-config-table-exclude-methods[|`allowOrigins`] -|`allow-origins`|`["*"]`|`Access-Control-Allow-Origins` - -ifndef::cors-config-table-exclude-methods[|`exposeHeaders`] -|`expose-headers`|`none`|`Access-COntrol-Expose-Headers` - -ifndef::cors-config-table-exclude-methods[|`maxAgeSeconds`] -|`max-age`|`3600`|`Access-Control-Max-Age` - -ifndef::cors-config-table-exclude-methods[|`enabled`] -|`enabled`|`true`|n/a| -|==================== - -If the cross-origin configuration is disabled (`enabled` = false), then the Helidon CORS implementation ignores the cross-origin configuration entry. -// end::cors-config-table[] - -== Sample Routing Setup Using the CrossOriginConfig API - -In the link:{quickstart-example}[Helidon SE Quickstart application] you can change the greeting by sending a PUT request to the `/greet/greeting` resource. - -In this example, we use the low-level `CrossOriginConfig` API and the `CorsSupport` API to influence the <>, -thereby restricting how that resource is shared. - -To understand how to use configuration instead of the low-level API, see <>. - -The following code shows how to prepare your application's routing to support metrics and health support as well as -CORS. +link:{cors-spec}[Cross-origin resource sharing] (CORS) support in Helidon SE provides a flexible +mechanism that allows a Helidon SE application to control how another web application can access its resources, +even if that web application is served from a different domain. -[[intro-quick-start-code-example]] -[source,java] ----- - private static Routing createRouting(Config config) { +== Overview +include::{common-page-prefix-inc}[tag=cors-intro] - MetricsSupport metrics = MetricsSupport.create(); - GreetService greetService = new GreetService(config); - HealthSupport health = HealthSupport.builder() - .addLiveness(HealthChecks.healthChecks()) // Adds a convenient set of checks - .build(); - CorsSupport corsSupport = CorsSupport.builder() // <1> - .addCrossOriginConfig(CrossOriginConfig.builder() // <2> - .allowOrigins("http://foo.com", "http://there.com") // <3> - .allowMethods("PUT", "DELETE") // <4> - .build()) - .addCrossOriginConfig(CrossOriginConfig.create()) // <5> - .build(); - // Note: Add the CORS routing *before* registering the GreetService routing. - return Routing.builder() - .register(JsonSupport.create()) - .register(health) // Health at "/health" - .register(metrics) // Metrics at "/metrics" - .register("/greet", corsSupport, greetService) // <6> - .build(); - } ----- -<1> Create a `CorsSupport` instance using its builder. -<2> Add a `CrossOriginSupport` instance (using _its_ builder) to constrain resource sharing. -<3> List the origins (sites) allowed to share resources from this app. -<4> List the HTTP methods the constraint applies to. -<5> Add a `CrossOriginSupport` instance that permits all sharing (the default). -<6> Register the new `CorsSupport` instance with -- but in front of -- the service which implements the business logic. -The few additional lines identified above allow the application to participate in CORS. +== Next Steps +To introduce CORS into your Helidon SE application, do any or all of the following: -== Next Steps -See the Helidon CORS support in action by building and running the link:{helidon-se-cors-example}[CORS example]. +* Modify your code using the Helidon SE CORS API. <> +* Use configuration in combination with the Helidon SE CORS API to add CORS to your application. <> +* Update your application to include any of the built-in Helidon services that automatically +support CORS. <> diff --git a/docs/se/cors/02_configuration.adoc b/docs/se/cors/02_configuration.adoc deleted file mode 100644 index 5fad1509708..00000000000 --- a/docs/se/cors/02_configuration.adoc +++ /dev/null @@ -1,121 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// - - Copyright (c) 2020 Oracle and/or 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. - -/////////////////////////////////////////////////////////////////////////////// - -= Using Configuration for CORS -:javadoc-base-url-api: {javadoc-base-url}io.helidon.webserver.cors/io/helidon/webserver/cors -:javadoc-base-url-webserver: {javadoc-base-url}io.helidon.webserver/io/helidon/webserver -:pagename: cors-configuration -:description: Helidon CORS Configuration -:keywords: helidon, java, cors, configuration -:cors-se-intro: 01_introduction.adoc -:cors-config-table-src: {cors-se-intro} -:mapped-config-top-key: my-cors -:cors-config-table-exclude-methods: - -Your application can use configuration in setting up CORS behavior. - -== Understanding CORS Configuration Formats [[cors-configuration-formats]] -The support in Helidon for CORS configuration works with two closely-related formats. Each corresponds to a class -in the Helidon CORS library which holds CORS information. - -=== Basic Cross-Origin Configuration -Cross-origin configuration is the basic building block of CORS information. - -[[config-key-table]] -include::{cors-config-table-src}[tag=cors-config-table] - -The following example, when loaded and used by the application, limits cross-origin resource sharing for `PUT` and -`DELETE` operations to only `foo.com` and `there.com`: - -[source,hocon] ----- -... -restrictive-cors: - allow-origins: ["http://foo.com", "http://there.com"] - allow-methods: ["PUT", "DELETE"] -... ----- -// tag::mapped-config[] - -=== Mapped Cross-Origin Configuration -In some cases, you or your users might want to configure CORS behavior based on URL path matching. The following example of the _mapped_ -configuration format illustrates this. - -[source,hocon,subs="attributes+"] ----- -... -{mapped-config-top-key}: - paths: - - path-prefix: /greeting <1> - allow-origins: ["http://foo.com", "http://there.com", "http://other.com"] - allow-methods: ["PUT", "DELETE"] - - path-prefix: / <2> - allow-methods: ["GET", "HEAD", "OPTIONS", "POST"] -... ----- -<1> Sets restrictions on CORS for the `/greeting` path. -<2> Permits sharing of resources at the top-level path with all origins (the default) for the indicated HTTP methods. -CORS would be denied to `PATCH` HTTP requests. - -Path expressions can be any expression accepted by the -link:{javadoc-base-url-webserver}/PathMatcher.html[`PathMatcher`] class. - -Arrange the entries in the order that you want Helidon to check them. Usually this is from most specific to most general. -Helidon CORS support searches the cross-origin entries in the order you define them until it finds an entry that -matches an incoming request's path expression and HTTP method. -// end::mapped-config[] - -You might want to avoid using mapped configuration to set up the _normal_ CORS behavior in your application, because you have to -make sure the paths are the same in the configuration and in the code that establishes routing for your application. -You _can_ use mapped configuration to your advantage if you want to allow your users to override the CORS behavior set up -in the application code. See the example below. - -== Using CORS Configuration From the Application -To use basic or mapped CORS configuration, you add logic to your application to load sections of configuration and -use the loaded config to build `CrossOriginConfig` instances. (The -<> shows -this in practice.) - -You can create -a `CrossOriginConfig` instance directly from a config node, as shown in this example. - -[source,java] ----- -CorsSupport.Builder builder = CorsSupport.builder(); - -Config config = Config.create(); // Created from the current config sources - -config.get("my-cors") // <1> - .ifExists(builder::mappedConfig); - -config.get("restrictive-cors") // <2> - .ifExists(builder::config); - -builder.addCrossOriginConfig(CrossOriginConfig.create()); // <3> - -CorsSupport corsSupport = builder.build(); // <4> ----- -<1> If `my-cors` exists in the configuration, use it to add mapped CORS config to the `CorsSupport` builder. -<2> If `restrictive-cors` exists in the configuration, use it to add basic (not mapped) config to the builder. -<3> Provide default CORS handling for requests that do not match earlier entries. -<4> Obtain the finished `CorsSupport` instance, suitable for use in creating the application's routing. - -As each request arrives, Helidon checks it against the cross-origin config instances in the order that your application added them to the `CorsSupport.Builder`. The `my-cors` mapped configuration acts as an override because the application added it to the builder first. - -== Next Steps -See the Helidon CORS support in action by building and running the link:{helidon-se-cors-example}[CORS example]. diff --git a/docs/se/cors/02_using-the-api.adoc b/docs/se/cors/02_using-the-api.adoc new file mode 100644 index 00000000000..1edefb823cc --- /dev/null +++ b/docs/se/cors/02_using-the-api.adoc @@ -0,0 +1,178 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2020 Oracle and/or 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. + +/////////////////////////////////////////////////////////////////////////////// + += Using the Helidon SE CORS API +:toc: +:toc-placement: preamble +:h1Prefix: SE +:pagename: cors-introduction +:description: Using the Helidon SE CORS API +:keywords: helidon, java, cors, se, api +:helidon-tag: https://github.com/oracle/helidon/tree/{helidon-version} +:quickstart-example: {helidon-tag}/examples/quickstarts/helidon-quickstart-se +:cors-spec: https://www.w3.org/TR/cors/ +:helidon-se-cors-example: {helidon-tag}/examples/cors +:helidon-tag: https://github.com/oracle/helidon/tree/{helidon-version} +:quickstart-example: {helidon-tag}/examples/quickstarts/helidon-quickstart-se +:javadoc-base-url-api: {javadoc-base-url}io.helidon.webserver.cors/io/helidon/webserver/cors +:javadoc-base-url-webserver: {javadoc-base-url}io.helidon.webserver/io/helidon/webserver +:helidon-variant: SE +:config-table-methods-column-header: Method +:cors-config-table-exclude-keys: +:common-page-prefix-inc: ../../shared/cors/common_shared.adoc + +Every Helidon SE application explicitly creates routing rules that govern how Helidon delivers each incoming + request to the code that needs to respond. The Helidon CORS SE API provides a simple way to include CORS into + the routing rules that you construct for your application. + +== Understanding the Helidon SE CORS API + +To add CORS behavior to endpoints, you need to make only minimal changes to how you set up the routing for those endpoints. +Using the Helidon SE CORS API, +you define the CORS behavior that you want and then include that behavior as you build the routing rules for the services +in your application. + +The Helidon SE CORS API provides two key classes that you use in your application: + +* `CorsSupport` - Represents information about resource sharing for a single resource. +Typically, you create one `CorsSupport` instance for each distinct resource in your application +(such as the `/greet` resource in the QuickStart greeting application) that should participate in CORS. + +* `CrossOriginConfig` - Represents the details for a particular type of sharing, such as which origins are +allowed to have access using which HTTP methods, etc. +Create one instance of `CrossOriginConfig` for each different type of sharing you need. + +You associate one or more `CrossOriginConfig` objects with each `CorsSupport` object. +You use the `CorsSupport` object when you construct the routing rules for the service. +When your application is running and requests arrive, the Helidon CORS implementation enforces +the CORS behavior represented by the `CorsSupport` object before routing the request to your +endpoint code for the resource. + +== Getting Started + +To add CORS support to your Helidon SE application: + +. Determine the type of cross origin sharing you want to allow for each endpoint in your +application. +. {blank} ++ +-- +Add a dependency on the Helidon {helidon-variant} CORS artifact to your Maven `pom.xml` file. + +include::{common-page-prefix-inc}[tag=add-cors-dependency] + +// tag::actual-cors-dependency[] +[source,xml,subs="attributes+"] +---- + + io.helidon.webserver + helidon-webserver-cors + +---- +// end::actual-cors-dependency[] +-- +. Modify how your application constructs routing rules so they include CORS as described in the following sections. + +== Adding CORS Support in Your Helidon SE Application [[adding-cors-support]] +Because Helidon SE does not use annotation processing to identify endpoints, you need to +provide the CORS information for your application another way - +by including CORS into the routing you construct for your application. + +For each distinct resource or subresource your application exposes: + +. Create a link:{javadoc-base-url-api}/CorsSupport.html[`CorsSupport`] instance corresponding to the resource. + +. For each different type of sharing you want to provide for that resource: + +.. Create a link:{javadoc-base-url-api}/CrossOriginConfig.html[`CrossOriginConfig`] instance. + +The `CrossOriginConfig` Java class represents the details for a particular type of sharing, such as +which origins are allowed to share via which HTTP methods, etc. + +.. Add the `CrossOriginConfig` to the `CorsSupport` instance for this resource. + +. Use the resource's `CorsSupport` object in setting up the routing rules for that resource. + +Each of these classes has an associated builder that you use in constructing instances of the class. + +The table below describes the methods on the `CrossOriginConfig.Builder` class +that map to the headers defined in the CORS protocol. + +include::{common-page-prefix-inc}[tag=cors-config-table] + +[[se-api-routing-example]] +== Sample Routing Setup Using the `CrossOriginConfig` API + +The link:{quickstart-example}[Helidon SE Quickstart application] lets you change the greeting by sending a `PUT` +request to the `/greet/greeting` resource. + +This example, based on the QuickStart greeting app, uses the low-level `CrossOriginConfig` API and +the `CorsSupport` API to influence the <>, +thereby determining how that resource is shared. (If desired, you can use configuration instead of the low-level API. +<>) + +The following code shows how to prepare your application's routing to support metrics and health support, as well as +CORS. + +[[intro-quick-start-code-example]] +[source,java] +---- + private static Routing createRouting(Config config) { + + MetricsSupport metrics = MetricsSupport.create(); + GreetService greetService = new GreetService(config); + HealthSupport health = HealthSupport.builder() + .addLiveness(HealthChecks.healthChecks()) // Adds a convenient set of checks + .build(); + CorsSupport corsSupport = CorsSupport.builder() // <1> + .addCrossOriginConfig(CrossOriginConfig.builder() // <2> + .allowOrigins("http://foo.com", "http://there.com") // <3> + .allowMethods("PUT", "DELETE") // <4> + .build()) // <5> + .addCrossOriginConfig(CrossOriginConfig.create()) // <6> + .build(); // <7> + + // Note: Add the CORS routing *before* registering the GreetService routing. + return Routing.builder() + .register(JsonSupport.create()) + .register(health) // Health at "/health" + .register(metrics) // Metrics at "/metrics" + .register("/greet", corsSupport, greetService) // <8> + .build(); + } +---- +<1> Create a `CorsSupport.Builder` instance. +<2> Add a `CrossOriginSupport` instance (using _its_ builder) to constrain resource sharing. +<3> List the origins (sites) allowed to share resources from this app. +<4> List the HTTP methods the constraint applies to. +<5> Build the `CrossOriginSupport` instance. +<6> Add a `CrossOriginSupport` instance that permits all sharing (the default). +<7> Build the `CorsSupport` instance. +<8> Register the new `CorsSupport` instance with -- but in front of -- the service which implements the business logic. + +The order of steps 2 and 6 above is important. When processing an incoming request, the Helidon CORS implementation +scans the `CrossOriginConfig` instances in the order they were added to the `CorsSupport` object, stopping as soon as +it finds a `CrossOriginConfig` instance for which `allowMethods` matches the HTTP method of the +request. + +The few additional lines described above allow the greeting application to participate in CORS. + +== Next Steps +* Use configuration in combination with the API to add CORS to your application. +<> + +* See the Helidon CORS support in action by building and running the link:{helidon-se-cors-example}[CORS example]. diff --git a/docs/se/cors/03_using-configuration.adoc b/docs/se/cors/03_using-configuration.adoc new file mode 100644 index 00000000000..49788287e27 --- /dev/null +++ b/docs/se/cors/03_using-configuration.adoc @@ -0,0 +1,129 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2020 Oracle and/or 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. + +/////////////////////////////////////////////////////////////////////////////// + += Using Configuration for CORS +:javadoc-base-url-api: {javadoc-base-url}io.helidon.webserver.cors/io/helidon/webserver/cors +:javadoc-base-url-webserver: {javadoc-base-url}io.helidon.webserver/io/helidon/webserver +:h1Prefix: SE +:pagename: cors-configuration +:description: Helidon CORS Configuration +:keywords: helidon, java, cors, configuration +:cors-se-intro: 01_introduction.adoc +:mapped-config-top-key: my-cors +:helidon-tag: https://github.com/oracle/helidon/tree/{helidon-version} +:helidon-se-cors-example: {helidon-tag}/examples/cors +:mapped-config-id-callout: Assigns a unique identifier for this mapped CORS config section. +:common-page-prefix-inc: ../../shared/cors/common_shared.adoc +:cors-config-table-exclude-methods: +:!cors-config-table-exclude-keys: +:basic-table-intro: The table below lists the configuration keys that identify the CORS characteristics. +:helidon-variant: SE + +You can use configuration in combination with the Helidon CORS SE API to add CORS support to your resources by +replacing some Java code with declarative configuration. This also gives your users a way to override the +CORS behavior of your services without requiring the code to change. + +include::{common-page-prefix-inc}[tag=cors-configuration-formats-intro] + +include::{common-page-prefix-inc}[tag=basic-cross-origin-config] + + +=== Mapped Cross-Origin Configuration +In some cases, you or your users might want to configure CORS behavior based on URL path matching. +// We want to use the following to insert the SE or MP callout 1 text; we need to use the blank, plus, +// and subs because the MP attribute value contains backticks, and this is the only way we've found +// to for the substitution in the callout to work the way we want. And this works when +// rendered in our editing tools and via the asciidoctor command to HTML but not on our built site. +// +// <1> {blank} +// + +// [subs=attributes+] +// {mapped-config-id-callout} +// +// So instead we have the prefix and suffix tags and the including document provides its own callout 1. +// If at some point the rendering for our site handles this, we can just remove the tag and end +// for the prefix and suffix and just have the including file include the mapped-config instead of +// include the prefix, then provide its own callout 1, then include the suffix. +// +include::{common-page-prefix-inc}[tag=mapped-config-prefix] +<1> {mapped-config-id-callout} +include::{common-page-prefix-inc}[tag=mapped-config-suffix] + +[[using-config-from-app]] +== Using CORS Configuration in the Application +You use configuration in combination with the Helidon CORS SE API +to add CORS support to your resources. The example in <> +uses the low-level Helidon CORS SE API to create +a `CrossOriginConfig` instance that is then used as part of a `CorsSupport` instance to create the routing rules. +As an alternative to using the low-level API, this example uses config to create the +`CrossOriginConfig` instance instead. + + +[source,java] +---- + private static Routing createRouting(Config config) { + + MetricsSupport metrics = MetricsSupport.create(); + GreetService greetService = new GreetService(config); + HealthSupport health = HealthSupport.builder() + .addLiveness(HealthChecks.healthChecks()) // Adds a convenient set of checks + .build(); + CorsSupport.Builder builder = CorsSupport.builder(); + + Config config = Config.create(); // Created from the current config sources + config.get("my-cors") // <1> + .ifExists(builder::mappedConfig); + config.get("restrictive-cors") // <2> + .ifExists(builder::config); + builder.addCrossOriginConfig(CrossOriginConfig.create()); // <3> + + CorsSupport corsSupport = builder.build(); // <4> + + // Note: Add the CORS routing *before* registering the GreetService routing. + return Routing.builder() + .register(JsonSupport.create()) + .register(health) // Health at "/health" + .register(metrics) // Metrics at "/metrics" + .register("/greet", corsSupport, greetService) // <5> + .build(); + } +---- +<1> If `my-cors` exists in the configuration, use it to add mapped CORS config to the `CorsSupport` builder. +<2> If `restrictive-cors` exists in the configuration, use it to add basic (not mapped) config to the builder. +<3> Provide default CORS handling for requests that do not match earlier entries. +<4> Obtain the finished `CorsSupport` instance. +<5> Use `corsSupport` in constructing the routing rules. + + +As each request arrives, Helidon checks it against the cross-origin config instances in the order that your application +added them to the `CorsSupport.Builder`. +The `my-cors` mapped configuration acts as an override because the application added it to the builder first. + +If the `my-cors` config key does not appear in the configuration, then the code skips creating a `CrossOriginConfig` +instance based on that configuration, and no overriding occurs. The CORS behavior +that is established by the other `CrossOriginConfig` instance based on the `restrictive-cors` config (if present) +prevails. + +NOTE: Remember that if you set configuration in a file that you include as part of your application JAR file, then you need to +rebuild and restart your application for any changes to take effect. + +== Next Steps + +* Use these same configuration techniques to control the behavior of the CORS-enabled built-in services. +<> +* See the Helidon CORS support in action by building and running the link:{helidon-se-cors-example}[CORS example]. diff --git a/docs/se/cors/04_support-in-builtin-services.adoc b/docs/se/cors/04_support-in-builtin-services.adoc new file mode 100644 index 00000000000..fa89b61ba7b --- /dev/null +++ b/docs/se/cors/04_support-in-builtin-services.adoc @@ -0,0 +1,111 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2020 Oracle and/or 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. + +/////////////////////////////////////////////////////////////////////////////// + += Using CORS in Built-in Services +:toc: +:toc-placement: preamble +:h1Prefix: SE +:pagename: cors-built-in-service-support +:description: Helidon SE CORS Support in Built-in Services +:keywords: helidon, java, cors, se, services +:helidon-tag: https://github.com/oracle/helidon/tree/{helidon-version} +:quickstart-example: {helidon-tag}/examples/quickstarts/helidon-quickstart-se +:cors-spec: https://www.w3.org/TR/cors/ +:helidon-se-cors-example: {helidon-tag}/examples/cors +:helidon-tag: https://github.com/oracle/helidon/tree/{helidon-version} +:quickstart-example: {helidon-tag}/examples/quickstarts/helidon-quickstart-se +:javadoc-base-url-api: {javadoc-base-url}io.helidon.webserver.cors/io/helidon/webserver/cors +:javadoc-base-url-webserver: {javadoc-base-url}io.helidon.webserver/io/helidon/webserver +:cors-se-api-doc: ../../se/cors/02_using-the-api.adoc +:cors-config-table-src: {cors-se-api-doc} +:cors-dependency-src: {cors-se-api-doc} +:actual-cors-dependency-src: {cors-se-api-doc} +:health-page: se/health/01_health.adoc +:metrics-page: se/metrics/01_metrics.adoc +:openapi-page: se/openapi/01_openapi.adoc +:cors-config: ../../se/cors/03_using-configuration.adoc +:cors-services-is-se: +:helidon-variant: SE +:common-page-prefix-inc: ../../shared/cors/common_shared.adoc + +Several built-in Helidon services -- health, metrics, and OpenAPI -- have integrated CORS support. +You can include these services in your application and control their CORS behavior. + + +include::{common-page-prefix-inc}[tag=understanding-cors-support-in-services] + +include::{common-page-prefix-inc}[tag=builtin-getting-started] + +== Controlling CORS for Built-in Services Using the API +Although services such as health, metrics, and OpenAPI are built into Helidon, to use them your application must create +instances of the services and then use those instances in building your application's routing rules. + +Recall that each +service type has a `Builder` class. To control the CORS behavior of a built-in service using the API, follow these steps: + +. Create a `Builder` for the type of service of interest. + +. Build an instance of `CrossOriginConfig` with the settings you want. + +. Invoke the `builder.crossOriginConfig` method, passing that `CrossOriginConfig` instance. + +. Invoke the builder's `build` method to initialize the service instance. + +. Use the service instance in preparing the routing rules. + +The following excerpt shows changes to the link:{quickstart-example}[Helidon SE QuickStart example] which limit +sharing of the `/metrics` endpoint to `\http://foo.com`. +[source,java] +---- +private static Routing createRouting(Config config) { + + CrossOriginConfig metricsCrossOriginConfig = CrossOriginConfig.builder() // <1> + .allowOrigins("http://foo.com") + .build(); + MetricsSupport metrics = MetricsSupport.builder() + .crossOriginConfig(metricsCrossOriginConfig) // <2> + .build(); + GreetService greetService = new GreetService(config); + HealthSupport health = HealthSupport.builder() + .addLiveness(HealthChecks.healthChecks()) // Adds a convenient set of checks + .build(); + + return Routing.builder() + .register(health) // Health at "/health" + .register(metrics) // Metrics at "/metrics" // <3> + .register("/greet", greetService) + .build(); + } +---- +<1> Create the `CrossOriginConfig` for metrics, limiting sharing to `\http://foo.com`. +<2> Use the `CrossOriginConfig` instance in constructing the `MetricsSupport` service. +<3> Use the `MetricsSupport` object in creating the routing rules. + +include::{common-page-prefix-inc}[tag=configuring-cors-for-builtin-services] + +include::{common-page-prefix-inc}[tag=accessing-shared-resources-intro] + +[source,bash] +---- +mvn package +java -jar target/helidon-quickstart-se.jar +... +WEB server is up! http://localhost:8080/greet +---- + +include::{common-page-prefix-inc}[tag=accessing-shared-resources-main] diff --git a/docs/se/dbclient/01_introduction.adoc b/docs/se/dbclient/01_introduction.adoc index 529af61a81f..2fc7d7359eb 100644 --- a/docs/se/dbclient/01_introduction.adoc +++ b/docs/se/dbclient/01_introduction.adoc @@ -16,10 +16,244 @@ /////////////////////////////////////////////////////////////////////////////// -= Helidon DB Client -:toc: -:toc-placement: preamble += About Helidon DB Client + :description: Helidon DB Client :keywords: helidon, se, database, dbclient +:h1Prefix: SE & MP + + + +The Helidon SE DB Client provides a unified, reactive API for working with databases in non-blocking way. + +== Helidon DB Client Features Overview + +The DB Client simplfies how you work with databases by allowing the use of blocking JDBC drivers in your reactive application by wrapping a blocking driver in an executor service. Specifically, the Heldion DB Client provides the following: + +* Unified API for data access and query ++ +The API was implemented as a layer above JDBC or MongoDB Reactive Streams Java Driver, so any relational databases with JDBC driver or MongoDB are supported. + +* Reactive database access with non-reactive drivers ++ +Most JDBC drivers are blocking. Using them in a reactive application is problematic. Helidon DB Client allows the use of blocking JDBC drivers in your reactive application by wrapping a blocking driver in an executor service. + +* Observability + ++ +The API offers support for health checks, metrics and tracing. + +* Backpressure Management ++ +Helidon DB Client performs database operations only when it’s requested by the consumer. This is propagated all the way to the TCP layer. + +* Improved portability between database drivers ++ +The DB Client works with native database statements that can be used inline in the code or defined as named statements in database configuration. + +By moving the native query code to configuration files, the Helidon DB Client allows you to switch to another database by changing the configuration files, not the code. + +== Getting Started + +Before you begin you must add the DB Client dependencies and configure the client. + +. Add the DB Client dependencies to the Maven `pom.xml` file. ++ +The <> page describes how you +should declare dependency management for Helidon applications. For the DB Client you must include the following dependencies in your project: + ++ +[source,java] +---- + + + io.helidon.dbclient // <1> + helidon-dbclient + + + io.helidon.dbclient // <2> + helidon-dbclient-jdbc + + + com.h2.database // <3> + h2 + 1.4.200 + + ... + + +---- + ++ + +<1> Add the Helidon DB Client +<2> Specify JDBC or MongoDB +<3> Add the database details + +. Use Helidon Config to configure the client. ++ + +The DB Client must be configured before you begin. In the example below we'll use Helidon Config to set up your JDBC-based client: + ++ +[source,java] +---- + +db: + source: "jdbc" // <1> + connection: + url: "jdbc:mysql://127.0.0.1:3306/pokemon?useSSL=false" // <2> + username: "user" + password: "password" + poolName: "mysql" + statements: // <3> + ping: "DO 0" + select-all-pokemons: "SELECT id, name FROM Pokemons" + +---- + ++ +<1> Source: JDBC or MongoDB +<2> Connection: database connection parameters +<3> Statements: named statements when used + +== Using DB Client API Methods + +The Helidon DB Client API contains many methods to run various statements with parameters and to retrieve statement execution results. The following sections describe the options you can use to build and execute your statements. + +=== Executor Selection + +`DBClient` class has two methods to select whether statements will be executed in transaction or not: + +* `execute(Function executor)` + +* `inTransaction(Function> executor)` + +Both methods require `Function` interface argument with statements `executor`. + +=== Statement Building and Execution +DbExecute class offers many methods for various statements builders: + +* DML statements: `createDmlStatement`, `createNamedDmlStatement` +* insert statements: `createInsert`, `createNamedInsert` +* update statements: `createUpdate`, `createNamedUpdate` +* delete statements: `createDelete`, `createNamedDelete` +* query statements: `createQuery`, `createNamedQuery` +* common statements: `createStatement`, `createNamedStatement` + +Methods with "Named" in their name (`create**Named**DmlStatement`) expect statement name from statements section of Config. + +All statement builders offer methods to set statement parameters. Those parameters can be ordered parameters or named parameters. Ordered and named parameters can’t be mixed in a single statement. + +=== Ordered Parameters + +Ordered parameters are written down as `?` in the SQL statement: + +---- +SELECT name FROM Pokemons WHERE id = ? +---- + + +The ordered parameters are equivalent to JDBC `PreparedStatement` parameters. + + +Methods to set ordered parameters are: + +* `params(List parameters)` with all parameters as List +* `params(Object… parameters)` with all parameters as array +* `indexedParam(Object parameters)` POJO used with registered mapper +* `addParam(Object parameter)` with single parameter, can be called repeatedly + +=== Named Parameters +Named parameters are written down as :`` in the SQL statement: + +---- +SELECT name FROM Pokemons WHERE id = :id +---- + +or as `$` in the MongoDB statement: + +---- +{ + "collection": "pokemons", + "operation": "update", + "value":{ $set: { "name": $name } }, + "query": { id: $id } +} +---- + +Methods to set named parameters are: + +* `params(Map parameters)` with all parameters as Map +* `namedParam(Object parameters)` POJO used with registered mapper +* `addParam(String name, Object parameter)` with single parameter, can be called repeatedly + +=== Statement Execution + +Statements are executed by calling execute() method after statement parameters are set. This method returns `CompletionStage` where `R` is the statement execution result. + +JDBC query with ordered parameters and query that does not run in the transaction: + +---- +dbClient.execute(exec -> exec + .createQuery("SELECT name FROM Pokemons WHERE id = ?") + .params(1) + .execute() +); +---- + +JDBC query with named parameters and the query runs in transaction: + +---- +dbClient.inTransaction(tx -> tx + .createQuery("SELECT name FROM Pokemons WHERE id = :id") + .addParam("id", 1) + .execute() +); +---- + +Both examples will return `CompletionStage>` with rows returned by the query. + +This example shows a MongoDB update statement with named parameters and the query does not run in transaction: + +---- +dbClient.execute(exec -> exec + .createUpdate("{\"collection\": \"pokemons\"," + + "\"value\":{$set:{\"name\":$name}}," + + "\"query\":{id:$id}}") + .addParam("id", 1) + .addParam("name", "Pikachu") + .execute() +); +---- + +This update statement will return `CompletionStage` with the number of modified records in the database. + +==== DML Statement Result + +Execution of DML statements will always return `CompletionStage` with the number of modified records in the database. + +In following example, the number of modified records is being printed to standard output: + +---- +dbClient.execute(exec -> exec + .insert("INSERT INTO Pokemons (id, name) VALUES(?, ?)", + 1, "Pikachu")) + .thenAccept(count -> + System.out.printf("Inserted %d records, count\n")); + +---- + +==== Query Statement Result + +Execution of a query statement will always return `CompletionStage>`. Class `DbRows` offers several methods to access this result: + +* `Flow.Publisher publisher()` to process individual result rows using `Flow.Subscriber` +* `CompletionStage> collect()` to collect all rows and return them as `List` +* ` DbRows map(…)` to map returned result using provided mapper + +== Next Steps + +Now that you understand how to build and execute statements, try it for yourself. https://github.com/oracle/helidon/tree/master/examples/dbclient[DB Client Examples]. + + + -== This page is Under Construction and will be available soon diff --git a/docs/se/guides/15_migration.adoc b/docs/se/guides/15_migration.adoc new file mode 100644 index 00000000000..263d07b8f92 --- /dev/null +++ b/docs/se/guides/15_migration.adoc @@ -0,0 +1,255 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2020 Oracle and/or its affiliates. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +/////////////////////////////////////////////////////////////////////////////// + += Helidon SE Migration Guide +:description: Helidon Migration Guide +:keywords: helidon, porting, migration, incompatibilities +:helidon-uc-flavor: SE +:helidon-lc-flavor: se + +In Helidon 2 we have made some changes to APIs and runtime behavior. This guide +will help you migrate a Helidon SE 1.x application to 2.x. + +include::../../common/guides/migration.adoc[] + +== Configuration + +1. File watching is now done through a `ChangeWatcher` - use of `PollingStrategies.watch()` needs to be refactored to + `FileSystemWatcher.create()` and the method to configure it on config source builder has changed to + `changeWatcher(ChangeWatcher)`. +2. Methods on `ConfigSources` now return specific builders (they used to return `AbstractParsableConfigSource.Builder` + with a complex type declaration). If you store such a builder in a variable, either change it to the correct type, + or use `var` +3. Some APIs were cleaned up to be aligned with the development guidelines of Helidon. When using Git config source, + or etcd config source, the factory methods moved to the config source itself, and the builder now accepts all + configuration options through methods +4. The API of config source builders has been cleaned, so now only methods that are relevant to a specific config + source type can be invoked on such a builder. Previously you could configure a polling strategy on a source that + did not support polling +5. There is a small change in behavior of Helidon Config vs. MicroProfile Config: + The MP TCK require that system properties are fully mutable (e.g. as soon as the property is changed, it + must be used), so MP Config methods work in this manner (with a certain performance overhead). + Helidon Config treats System properties as a mutable config source, with a (optional) time based polling strategy. So + the change is reflected as well, though not immediately (this is only relevant if you use change notifications). +6. `CompositeConfigSource` has been removed from `Config`. If you need to configure `MerginStrategy`, you can do it now on + `Config` `Builder` + +Example of advanced configuration of config: +```java +Config.builder() + // system properties with a polling strategy of 10 seconds + .addSource(ConfigSources.systemProperties() + .pollingStrategy(PollingStrategies.regular(Duration.ofSeconds(10)))) + // environment variables + .addSource(ConfigSources.environmentVariables()) + // optional file config source with change watcher + .addSource(ConfigSources.file(Paths.get("/conf/app.yaml")) + .optional() + .changeWatcher(FileSystemWatcher.create())) + // classpath config source + .addSource(ConfigSources.classpath("application.yaml")) + // map config source (also supports polling strategy) + .addSource(ConfigSources.create(Map.of("key", "value"))) + .build(); +``` + + +== Resource class when loaded from Config + +The configuration approach to `Resource` class was using prefixes which was not aligned with our approach to configuration. +All usages were refactored as follows: + +1. The `Resource` class expects a config node `resource` that will be used to read it +2. The feature set remains unchanged - we support path, classpath, url, content as plain text, and content as base64 +3. Classes using resources are changed as well, such as `KeyConfig` - see details below + +== Media Support + +In Helidon 1.x support for JSON and other media types was configured when constructing +`webserver.Routing` using the `register` method. In Helidon 2 Media Support has been +refactored so that it can be shared between the Helidon `WebServer` and `WebClient`. +You now specify media support as part of the WebServer build: + +```java +WebServer.builder() + .addMediaSupport(JsonpSupport.create()) //registers reader and writer for Json-P + .build() +``` + +This replaces `Routing.builder().register(JsonSupport.create())...` + +The new JSON MediaSupport classes are: + +* `io.helidon.media.jsonp.JsonpSupport` in module `io.helidon.media:helidon-media-jsonp` +* `io.helidon.media.jsonb.JsonbSupport` in module `io.helidon.media:helidon-media-jsonb` +* `io.helidon.media.jackson.JacksonSupport` in module `io.helidon.media:helidon-media-jackson` + + +== Reactive + +|=== +|Removed | Replacement + +|`io.helidon.common.reactive.ReactiveStreamsAdapter`|`org.reactivestreams.FlowAdapters` +|=== + +== Security: OidcConfig + +Configuration has been updated to use the new `Resource` approach: + +1. `oidc-metadata.resource` is the new key for loading `oidc-metadata` from local resource +2. `sign-jwk.resource` is the new key for loading signing JWK resource + +== Security: JwtProvider and JwtAuthProvider + +Configuration has been updated to use the new `Resource` approach: + +1. `jwk.resource` is the new key for loading JWK for verifying signatures +2. `jwt.resource` is also used for outbound as key for loading JWK for signing tokens + +== PKI Key Configuration + +The configuration has been updated to have a nicer tree structure: + +Example of a public key from keystore: +```yaml +keystore: + cert.alias: "service_cert" + resource.path: "/conf/keystore.p12" + type: "PKCS12" + passphrase: "password" +``` + +Example of a private key from keystore: +```yaml +keystore: + key: + alias: "myPrivateKey" + passphrase: "password" + resource.resource-path: "keystore/keystore.p12" + passphrase: "password" +``` + +Example of a pem resource with private key and certificate chain: +```yaml +pem: + key: + passphrase: "password" + resource.resource-path: "keystore/id_rsa.p8" + cert-chain: + resource.resource-path: "keystore/public_key_cert.pem" +``` + + +== GrpcTlsDescriptor + +Configuration has been updated to use the new `Resource` approach: + +1. `tls-cert.resource` is the new key for certificate +2. `tls-key.resource` is the new key for private key +3. `tl-ca-cert` is the the new key for certificate + +== WebServer Configuration + +=== SSL/TLS +There is a new class `io.helidon.webserver.TlsConfig` that can be used +to configure TLS for a WebServer socket. +Class `io.helidon.webserver.SSLContextBuilder` has been deprecated and will +be removed. + +The class uses a `Builder` pattern: +```java +TlsConfig.builder() + .privateKey(KeyConfig.keystoreBuilder() + .keystore(Resource.create("certificate.p12")) + .keystorePassphrase("helidon") +``` + +The builder or built instance can be registered with a socket configuration builder +including the `WebServer.Builder` itself: + +```java +WebServer.builder(routing()) + .tls(tlsConfig) + .build(); +``` + +=== Additional Sockets + +Additional socket configuration has changed both in config +and in API. + +The configuration now accepts following structure: +```yaml +server: + port: 8000 + sockets: + - name: "admin" + port: 8001 + - name: "static" + port: 8002 + enabled: false +``` + +Socket name is now a value of a property, allowing more freedom in naming. +The default socket name is implicit (and set to `@default`). + +We have added the `enabled` flag to support disabling sockets through configuration. + +To add socket using a builder, you can use: + +```java +WebServer.builder() + .addSocket(SocketConfigurationBuidler.builder() + .port(8001) + .name("admin"))); +``` + +There is also a specialized method to add a socket and routing +together, to remove mapping through a name. + +=== Deprecation of ServerConfiguration + +`io.helidon.webserver.ServerConfiguration.Builder` is no longer used +to configure `WebServer`. + +Most methods from this class have been moved to `WebServer.Builder` or deprecated. + +Example of a simple WebServer setup: + +```java +WebServer.builder() + .port(8001) + .host("localhost") + .routing(createRouting()) + .build(); +``` + +=== Other significant WebServer deprecations + +- `io.helidon.webserver.WebServer.Builder` - all methods that accept `ServerConfiguration` or its builder are deprecated, please use + methods on `WebServer.Builder` instead +- `io.helidon.webserver.WebServer.Builder` - all methods for socket configuration that accept a name + and socket are deprecated, socket name is now part of socket configuration itself +- `io.helidon.webserver.ResponseHeaders.whenSend()` - please use `whenSent()` +- `io.helidon.webserver.Routing.createServer(ServerConfiguration)` - please use `WebServer.builder()` +- `io.helidon.webserver.Routing.createServer()` - please use `WebServer.builder()` +- `io.helidon.webserver.SocketConfiguration.DEFAULT` - use a builder to create a named configuration +- `io.helidon.webserver.SocketConfiguration.Builder.ssl(SSLContext) - use `TlsConfig` instead +- `io.helidon.webserver.SocketConfiguration.Builder.enabledSSlProtocols(String...) - use `TlsConfig` instead + diff --git a/docs/se/reactivestreams/02_engine.adoc b/docs/se/reactivestreams/02_engine.adoc index 30afa658c56..1aa99a350f7 100644 --- a/docs/se/reactivestreams/02_engine.adoc +++ b/docs/se/reactivestreams/02_engine.adoc @@ -17,7 +17,7 @@ /////////////////////////////////////////////////////////////////////////////// = Helidon Reactive Engine -:h1Prefix: Se & Mp +:h1Prefix: SE & MP :description: Dependecy-less reactive operators :keywords: helidon, reactive, streams, multi, single diff --git a/docs/se/webclient/01_introduction.adoc b/docs/se/webclient/01_introduction.adoc index 2014b6af500..a37f447fc4c 100644 Binary files a/docs/se/webclient/01_introduction.adoc and b/docs/se/webclient/01_introduction.adoc differ diff --git a/docs/se/webserver/08_json-support.adoc b/docs/se/webserver/08_json-support.adoc index 7e9d46d5007..41e57fe1e77 100644 --- a/docs/se/webserver/08_json-support.adoc +++ b/docs/se/webserver/08_json-support.adoc @@ -32,34 +32,36 @@ Declare the following dependency in your project: .Webserver JSON-P Dependency ---- - io.helidon.media.jsonp - helidon-media-jsonp-server + io.helidon.media + helidon-media-jsonp ---- === Usage -To enable JSON-P support, first register it with the route builder. +To enable JSON-P support, first register it with the web server. Then you can add routes that handle and return JSON. [source,java] -.Configure JsonSupport and use it for reading and writing of entities +.Configure JsonpSupport and use it for reading and writing of entities ---- -Routing.builder() - .register(JsonSupport.create()) // <1> - .post("/sayhello", Handler.create(JsonObject.class, this::sayHello)) // <2> - .build(); +JsonpSupport jsonbSupport = JsonpSupport.create(); // <1> +WebServer webServer = WebServer.builder() + .addMediaSupport(jsonpSupport) // <2> + .build(); ---- -<1> Register JsonSupport to enable transformation from and to `JsonObject` objects -<2> Register a handler that receives a `JsonObject` as its input. +<1> Register JsonpSupport to enable transformation from and to `JsonObject` objects +<2> Register that JsonpSupport instance to enable automatic +deserialization of Java objects from and serialization of Java objects +to JSON. [source,java] .Handler that receives and returns JSON objects ---- -private static final JsonBuilderFactory jsonFactory = Json.createBuilderFactory(Collections.emptyMap()); <1> +private static final JsonBuilderFactory JSON_FACTORY = Json.createBuilderFactory(Collections.emptyMap()); <1> private void sayHello(ServerRequest req, ServerResponse res, JsonObject json) { // <2> - JsonObject msg = jsonFactory.createObjectBuilder() // <3> + JsonObject msg = JSON_FACTORY.createObjectBuilder() // <3> .add("message", "Hello " + json.getString("name")) .build(); res.send(msg); // <4> @@ -80,10 +82,10 @@ curl --noproxy '*' -X POST -H "Content-Type: application/json" \ === Configuring Json Reader/Writer factories To configure JSON-P `JsonReaderFactory` and `JsonWriterFactory` that are used by - the `JsonSupport` instance, create the `JsonSupport` object: + the `JsonpSupport` instance, create the `JsonpSupport` object: [source,java] -.Create `JsonSupport` with the provided configuration +.Create `JsonpSupport` with the provided configuration ---- -JsonSupport.create(Map.of(JsonGenerator.PRETTY_PRINTING, false)) +JsonpSupport.create(Map.of(JsonGenerator.PRETTY_PRINTING, false)) ---- diff --git a/docs/se/webserver/09_jsonb-support.adoc b/docs/se/webserver/09_jsonb-support.adoc index ec316a6aa8f..46879c7051c 100644 --- a/docs/se/webserver/09_jsonb-support.adoc +++ b/docs/se/webserver/09_jsonb-support.adoc @@ -34,33 +34,28 @@ Declare the following dependency in your project: [source,xml,subs="verbatim,attributes"] ---- - io.helidon.media.jsonb - helidon-media-jsonb-server + io.helidon.media + helidon-media-jsonb ---- === Usage -To enable JSON-B support, first create and -https://helidon.io/docs/latest/apidocs/io/helidon/webserver/Routing.Builder.html#register-io.helidon.webserver.Service...-[register] -a -https://helidon.io/docs/latest/apidocs/io/helidon/media/jsonb/server/JsonBindingSupport.html[`JsonBindingSupport`] +To enable JSON-B support, first create and register a +https://helidon.io/docs/latest/apidocs/io/helidon/media/jsonb/JsonbSupport.html[`JsonbSupport`] instance with a -https://helidon.io/docs/latest/apidocs/io/helidon/webserver/Routing.Builder.html[`Routing.Builder`]. -`JsonBindingSupport` is a `Service`, so it will install its own -`Handler` that will provide serialization and deserialization services -using https://github.com/eclipse-ee4j/yasson[Yasson], an -implementation of the http://json-b.net/[JSON-B specification]. +https://helidon.io/docs/latest/apidocs/io/helidon/webserver/WebServer.Builder.html[`WebServer.Builder`]. [source,java] -.Create and register `JsonBindingSupport` first +.Registration of the `JsonbSupport` via `WebServer` ---- -final JsonBindingSupport jsonBindingSupport = JsonBindingSupport.create(); // <1> -final Routing.Builder routingBuilder = Routing.builder(); -routingBuilder.register(jsonBindingSupport); // <2> +JsonbSupport jsonbSupport = JsonbSupport.create(); // <1> +WebServer webServer = WebServer.builder() + .addMediaSupport(jsonbSupport) // <2> + .build(); ---- -<1> Create a `JsonBindingSupport` instance. This instance may be +<1> Create a `JsonbSupport` instance. This instance may be reused freely. -<2> Register that `JsonBindingSupport` instance to enable automatic +<2> Register that `JsonbSupport` instance to enable automatic deserialization of Java objects from and serialization of Java objects to JSON. diff --git a/docs/se/webserver/10_jackson-support.adoc b/docs/se/webserver/10_jackson-support.adoc index b02a795b36f..dea254fb83d 100644 --- a/docs/se/webserver/10_jackson-support.adoc +++ b/docs/se/webserver/10_jackson-support.adoc @@ -33,31 +33,24 @@ Declare the following dependency in your project: [source,xml,subs="verbatim,attributes"] ---- - io.helidon.media.jackson - helidon-media-jackson-server + io.helidon.media + helidon-media-jackson ---- === Usage -To enable Jackson support, first create and -https://helidon.io/docs/latest/apidocs/io/helidon/webserver/Routing.Builder.html#register-io.helidon.webserver.Service...-[register] -a -https://helidon.io/docs/latest/apidocs/io/helidon/media/jsonb/server/JacksonSupport.html[`JacksonSupport`] +To enable Jackson support, first create and register a +https://helidon.io/docs/latest/apidocs/io/helidon/media/jackson/JacksonSupport.html[`JacksonSupport`] instance with a -https://helidon.io/docs/latest/apidocs/io/helidon/webserver/Routing.Builder.html[`Routing.Builder`]. -`JacksonSupport` is a -https://helidon.io/docs/latest/apidocs/io/helidon/webserver/Service.html[`Service`], -so it will install its own -https://helidon.io/docs/latest/apidocs/io/helidon/webserver/Handler.html[`Handler`] -that will provide serialization and deserialization services using -https://github.com/FasterXML/jackson#jackson-project-home-github[Jackson]. +https://helidon.io/docs/latest/apidocs/io/helidon/webserver/WebServer.Builder.html[`WebServer.Builder`]. [source,java] -.Create and register `JacksonSupport` first +.Registration of the `JacksonSupport` via `WebServer` ---- -final JacksonSupport jacksonSupport = JacksonSupport.create(); // <1> -final Routing.Builder routingBuilder = Routing.builder(); -routingBuilder.register(jacksonSupport); // <2> +JacksonSupport jacksonSupport = JacksonSupport.create(); // <1> +WebServer webServer = WebServer.builder() + .addMediaSupport(jacksonSupport) // <2> + .build(); ---- <1> Create a `JacksonSupport` instance. This instance may be reused freely. diff --git a/docs/shared/cors/common_shared.adoc b/docs/shared/cors/common_shared.adoc new file mode 100644 index 00000000000..a13925db6df --- /dev/null +++ b/docs/shared/cors/common_shared.adoc @@ -0,0 +1,440 @@ +/////////////////////////////////////////////////////////////////////////////// + Copyright (c) 2020 Oracle and/or 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. + +/////////////////////////////////////////////////////////////////////////////// + + +//Contains content that is shared between multiple CORS pages. +:keywords: helidon, java, cors, se +:helidon-tag: https://github.com/oracle/helidon/tree/{helidon-version} +:quickstart-example: {helidon-tag}/examples/quickstarts/helidon-quickstart-se +:cors-spec: https://www.w3.org/TR/cors/ +:helidon-se-cors-example: {helidon-tag}/examples/cors +:helidon-tag: https://github.com/oracle/helidon/tree/{helidon-version} +:quickstart-example: {helidon-tag}/examples/quickstarts/helidon-quickstart-se +:javadoc-base-url-api: {javadoc-base-url}io.helidon.webserver.cors/io/helidon/webserver/cors +:javadoc-base-url-webserver: {javadoc-base-url}io.helidon.webserver/io/helidon/webserver +:helidon-variant: SE +:common-page-prefix-inc: +:actual-cors-dependency-src: + += CORS Shared content + +// tag::cors-intro[] +The CORS protocol helps developers control if and how REST resources served by their applications can be shared across origins. +Helidon {helidon-variant} includes an implementation of CORS that you can use to add CORS behavior +to the services you develop. You can define your application's CORS behavior programmatically using the Helidon CORS API alone, or +together with configuration. Helidon also provides three built-in services that add their +own endpoints to your application - health, metrics, and OpenAPI - that have integrated CORS support. +By adding very little code to your application, you control how all the resources in +your application -- the ones you write and the ones provided by the Helidon built-in services -- can be shared across origins. + +== Before You Begin +Before you revise your application to add CORS support, you need to decide what type of cross-origin sharing you want +to allow for each resource your application exposes. +For example, suppose for a given resource you want to allow unrestricted sharing for GET, HEAD, and POST requests +(what CORS refers to as "simple" requests), but permit other types of requests only from the two +origins `foo.com` and `there.com`. +Your application would implement two types of CORS sharing: more relaxed for the +simple requests and stricter for others. + +Once you know the type of sharing you want to allow for each of your resources -- including any from built-in +services -- you can change your application accordingly. +// end::cors-intro[] + +// The add-cors-dependency tag's contents is reused from other SE and MP pages. +// The actual dependency is different for SE and MP, so we tag it separately from the intro text so the +// MP pages can reuse this intro text but use their own "actual" dependency. We could have parameterized +// the groupID and artifactID but this approach allows the actual dependencies themselves to be +// in the source literally rather than parameterized. +// tag::add-cors-dependency[] +The <> page describes how you +should declare dependency management for Helidon applications. +For CORS support in Helidon {helidon-variant}, you must include +the following dependency in your project: +// end::add-cors-dependency[] + +//tag::cors-configuration-formats-intro[] +== Understanding the CORS Configuration Formats [[cors-configuration-formats]] +Support in Helidon for CORS configuration uses two closely-related cross-origin configuration formats: basic and mapped. +Each format corresponds to a class +in the Helidon CORS library. +The basic format corresponds to the link:{javadoc-base-url-api}/CrossOriginConfig.html[`CrossOriginConfig`] +class, and the mapped format corresponds to the +link:{javadoc-base-url-api}/MappedCrossOriginConfig.html[`MappedCrossOriginConfig`] class. +//end::cors-configuration-formats-intro[] + +//tag::basic-cross-origin-config[] +[[basic-cross-origin-config]] +=== Basic Cross-Origin Configuration +In configuration, Helidon represents basic CORS information as a section, identified by a configuration +key of your choosing, that contains +one or more key/value pairs. Each key-value pair assigns one characteristic of CORS behavior. + +[subs=attributes+] +{basic-table-intro} + +[[config-key-table]] +include::{common-page-prefix-inc}[tag=cors-config-table] + +The following example of basic cross-origin +ifeval::["{helidon-variant}" == "SE"] +configuration, when loaded and used by the application, +endif::[] +ifeval::["{helidon-variant}" != "SE"] +configuration +endif::[] +limits cross-origin resource sharing for `PUT` and +`DELETE` operations to only `foo.com` and `there.com`: + +[source,hocon] +---- +... +ifeval::["{helidon-variant}" == "SE"] +restrictive-cors: +endif::[] + allow-origins: ["http://foo.com", "http://there.com"] + allow-methods: ["PUT", "DELETE"] +... +---- +//end::basic-cross-origin-config[] + + +// The following table is parameterized. +// +// To exclude the first column of the table -- the method or annotation parameter list -- and +// the text that describes it, define the cors-config-table-exclude-methods attribute in the including file. +// The value does not matter. +// +// To exclude the second column -- the config keys -- and the text that describes it, define +// cors-config-table-exclude-keys in the including file. The value does not matter. +// +// To customize the text that explains the first column, set config-table-methods-column-explainer +// to the text you want inserted. +// +// To customize the column heading for the first column, set config-table-methods-column-header to +// the header you want used. +// +// tag::cors-config-table[] + +ifndef::cors-config-table-exclude-methods+cors-config-table-exclude-keys[] +[width="100%",options="header",cols="4*"] +endif::[] +ifdef::cors-config-table-exclude-methods[] +[width="100%",options="header",cols="3*"] +endif::[] +ifdef::cors-config-table-exclude-keys[] +[width="100%",options="header",cols="3*"] +endif::[] + +|==================== +ifndef::cors-config-table-exclude-methods[| {config-table-methods-column-header} ] +ifndef::cors-config-table-exclude-keys[| Configuration Key] +| Default | CORS Header Name + +ifndef::cors-config-table-exclude-methods[|`allowCredentials`] +ifndef::cors-config-table-exclude-keys[|`allow-credentials`] +|`false`|`Access-Control-Allow-Credentials` + +ifndef::cors-config-table-exclude-methods[|`allowHeaders`] +ifndef::cors-config-table-exclude-keys[|`allow-headers`] +|`["*"]`|`Access-Control-Allow-Headers` + +ifndef::cors-config-table-exclude-methods[|`allowMethods`] +ifndef::cors-config-table-exclude-keys[|`allow-methods`] +|`["*"]`|`Access-Control-Allow-Methods` + +ifndef::cors-config-table-exclude-methods[|`allowOrigins`] +ifndef::cors-config-table-exclude-keys[|`allow-origins`] +|`["*"]`|`Access-Control-Allow-Origins` + +ifndef::cors-config-table-exclude-methods[|`exposeHeaders`] +ifndef::cors-config-table-exclude-keys[|`expose-headers`] +|`none`|`Access-Control-Expose-Headers` + +ifndef::cors-config-table-exclude-methods[|`maxAgeSeconds`] +ifndef::cors-config-table-exclude-keys[|`max-age`] +|`3600`|`Access-Control-Max-Age` + +ifndef::cors-config-table-exclude-methods[|`enabled`] +ifndef::cors-config-table-exclude-keys[|`enabled`] +|`true`|n/a| +|==================== + +If the cross-origin configuration is disabled (`enabled` = false), then the Helidon CORS implementation ignores the cross-origin configuration entry. +// end::cors-config-table[] + +// tag::mapped-config[] +// tag::mapped-config-prefix[] +Helidon represents mapped CORS information as a section, identified by a configuration +key of your choosing, that contains: + +* An optional `enabled` setting which defaults to `true` and applies to the whole mapped CORS config section, and + +* An optional `paths` subsection containing zero or more entries, each of which contains: + +** a basic CORS config section, and + +** a `path-pattern` path pattern that maps that basic CORS config section to the resource(s) it affects. + +You can use mapped configuration to your advantage if you want to allow your users to override the CORS behavior set up +in the application code. + +The following example illustrates the mapped cross-origin configuration format. + +[source,hocon,subs="attributes+"] +---- +... +{mapped-config-top-key}: <1> + paths: <2> + - path-pattern: /greeting <3> + allow-origins: ["http://foo.com", "http://there.com", "http://other.com"] <4> + allow-methods: ["PUT", "DELETE"] + - path-pattern: / <5> + allow-methods: ["GET", "HEAD", "OPTIONS", "POST"] <6> +... +---- +// We want to use the following to insert the SE or MP callout 1 text; we need to use the blank, plus, +// and subs because the MP attribute value contains backticks, and this is the only way we've found +// for the substitution in the callout to work the way we want. And this works when +// rendered in our editing tools and via the asciidoctor command to HTML but not on our built site. +// +// <1> {blank} +// + +// [subs=attributes+] +// {mapped-config-id-callout} +// +// So instead we have the prefix and suffix tags and the including document provides its own callout 1. +// If at some point the rendering for our site handles this, we can just remove the tag and end +// for the prefix and suffix and just have the including file include the mapped-config instead of +// include the prefix, then provide its own callout 1, then include the suffix. +// +// end::mapped-config-prefix[] + +// tag::mapped-config-suffix[] +<2> Collects the sequence of entries, each of which maps a basic CORS config to a path pattern. +<3> Marks the beginning of an entry (the `-` character) and maps the associated basic CORS config +to the `/greeting` subresource (the `path-pattern` key and value). +<4> Begins the basic CORS config section for `/greeting`; it +restricts sharing via `PUT` and `DELETE` to the listed origins. +<5> Marks the beginning of the next entry (the `-` character) and maps the associated basic CORS config to +the top-level resource in the app (the `path-pattern` key and value). +<6> Begins the basic CORS config section for `/`; it permits sharing of resources at the top-level path with all origins +for the indicated HTTP methods. + +Path patterns can be any expression accepted by the link:{javadoc-base-url-webserver}/PathMatcher.html[`PathMatcher`] class. + +NOTE: Be sure to arrange the entries in the order that you want Helidon to check them. +Helidon CORS support searches the cross-origin entries in the order you define them until it finds an entry that +matches an incoming request's path pattern and HTTP method. + +// end::mapped-config-suffix[] +// end::mapped-config[] + +// tag::understanding-cors-support-in-services[] +== Understanding CORS Support in Helidon Services +Helidon lets you easily include <<{health-page},health>>, <<{metrics-page},metrics>>, and +<<{openapi-page},OpenAPI>> services in your Helidon application. +These services add endpoints to your application so that clients can retrieve information about it. +As with the application endpoints you write, these endpoints represent resources that can be shared across origins. + +For example, several websites related to OpenAPI run a web application in your browser. +You provide the URL for your application to the browser application. +The browser application uses the URL to retrieve the OpenAPI document +that describes the application's endpoints directly from your application. +The browser application then displays a user interface that you use to "drive" your application. That is, +you provide input, have the web application +send requests to your application endpoints, and then view the responses. +This scenario is exactly the situation CORS addresses: an application in the browser from one origin -- the user interface downloaded from the +website -- requests a resource from another origin -- the `/openapi` endpoint which Helidon's OpenAPI built-in +service automatically adds to your application. + +Integrating CORS support into these built-in services allows such third-party web sites and their browser applications -- or +more generally, apps from any other origin -- to work with your Helidon application. + +Because all three of these built-in Helidon services serve only `GET` endpoints, by default the +integrated CORS support in all three services permits +any origin to share their resources using `GET`, `HEAD`, and `OPTIONS` HTTP requests. You can customize the CORS set-up +for these built-in services independently from each other using +ifdef::cors-services-is-se[ either the Helidon API, configuration, or both.] +ifndef::cors-services-is-se[ configuration.] +You can use this override feature to control the CORS behavior of the built-in services even if you do not add CORS behavior +to your own endpoints. + +// end::understanding-cors-support-in-services[] + +// tag::builtin-getting-started[] +== Getting Started +To use built-in services with CORS support and customize the +CORS behavior: + +. Add the built-in service or services to your application. The health, metrics, and OpenAPI services automatically +include default CORS support. +. {blank} ++ +-- +Add a dependency on the Helidon {helidon-variant} CORS artifact to your Maven `pom.xml` file. + +NOTE: If you want the built-in services to support CORS, then you need to add the CORS dependency even if your own endpoints do not use CORS. + +include::{common-page-prefix-inc}[tag=add-cors-dependency] +include::{actual-cors-dependency-src}[tag=actual-cors-dependency] +-- +. Use +ifdef::cors-services-is-se[the Helidon API or] +configuration to customize the CORS behavior as needed. + +The documentation for the individual built-in services describes how to add each +service to your application, including +adding a Maven +ifdef::cors-services-is-se[dependency and including the service in your application's routing rules.] +ifndef::cors-services-is-se[dependency.] +In your +application's configuration file, the configuration for each service appears under its own key. +|==== +| Helidon Service Documentation | Configuration Key + +| <<{health-page}, health>> | `health` +| <<{metrics-page}, metrics>> | `metrics` +| <<{openapi-page}, OpenAPI>> | `openapi` +|==== + +The link:{quickstart-example}[Helidon {helidon-variant} QuickStart example] +uses these services, so you can use that as a template for your +own application, or use the example project itself to experiment with customizing the CORS +behavior in the built-in services. +// end::builtin-getting-started[] + +// tag::configuring-cors-for-builtin-services[] +== Configuring CORS for Built-in Services +You can +ifdef::cors-services-is-se[also ] +use configuration to control whether and how each of the built-in services works with CORS. + +ifdef::cors-services-is-se[] +Your application can pass configuration to the builder for each built-in service. +endif::[] +For the health, metrics, and OpenAPI services, your configuration can include a section for CORS. + +// Tag the following example so we can exclude it from MP which supplies its own complete example. +// tag::se-config-example[] +The following example restricts sharing of the +`/health` resource, provided by the health built-in service, to only the origin `\http://there.com`. +[source,hocon] +---- +... +health: + cors: + allow-origins: [http://there.com] +... +---- + +// end::se-config-example[] + +// tag::se-code-changes-for-builtin-services-config[] +Modify your application to load the `health` config node and use it to construct the `HealthSupport` service. +The following code shows this change in the the QuickStart SE example. +[source,java] +---- +HealthSupport health = HealthSupport.builder() + .config(config.get("health")) // <1> + .addLiveness(HealthChecks.healthChecks()) // Adds a convenient set of checks + .build(); +---- +<1> Use the `health` config section (if present) to configure the health service. + +// end::se-code-changes-for-builtin-services-config[] + +You have full control over the CORS configuration for a built-in Helidon service. Use a basic CORS config section +as described in +ifdef::cors-services-is-se[<>.] +ifndef::cors-services-is-se[<<{mp-cors-config-ref},Using Configuration with CORS in Helidon MP>>.] + + +// end::configuring-cors-for-builtin-services[] + +// tag::accessing-shared-resources-intro[] +== Accessing the Shared Resources +If you have edited the Helidon {helidon-variant} QuickStart application as described in the previous topics and saved your changes, +you can build and run the application. Once you do so you can execute `curl` commands to demonstrate the behavior changes +in the metric and health services with the addition of the CORS functionality. Note the addition of the +`Origin` header value in the `curl` commands, and the `Access-Control-Allow-Origin` in the successful responses. + +=== Build and Run the Application +Build and run the QuickStart application as usual. +// end::accessing-shared-resources-intro[] + +// tag::accessing-shared-resources-main[] +=== Retrieve Metrics +The metrics service rejects attempts to access metrics on behalf of a disallowed origin. +[source,bash] +---- +curl -i -H "Origin: http://other.com" http://localhost:8080/metrics + +HTTP/1.1 403 Forbidden +Date: Mon, 11 May 2020 11:08:09 -0500 +transfer-encoding: chunked +connection: keep-alive +---- + +But accesses from `foo.com` succeed. +[source,bash] +---- +curl -i -H "Origin: http://foo.com" http://localhost:8080/metrics + +HTTP/1.1 200 OK +Access-Control-Allow-Origin: http://foo.com +Content-Type: text/plain +Date: Mon, 11 May 2020 11:08:16 -0500 +Vary: Origin +connection: keep-alive +content-length: 6065 + +# TYPE base_classloader_loadedClasses_count gauge +# HELP base_classloader_loadedClasses_count Displays the number of classes that are currently loaded in the Java virtual machine. +base_classloader_loadedClasses_count 3568 +... +---- + +=== Retrieve Health +The health service rejects requests from origins not specifically approved. + +[source,bash] +---- +curl -i -H "Origin: http://foo.com" http://localhost:8080/health + +HTTP/1.1 403 Forbidden +Date: Mon, 11 May 2020 12:06:55 -0500 +transfer-encoding: chunked +connection: keep-alive +---- + +And responds successfully only to cross-origin requests from `\http://there.com`. + +[source,bash] +---- +curl -i -H "Origin: http://there.com" http://localhost:8080/health + +HTTP/1.1 200 OK +Access-Control-Allow-Origin: http://there.com +Content-Type: application/json +Date: Mon, 11 May 2020 12:07:32 -0500 +Vary: Origin +connection: keep-alive +content-length: 461 + +{"outcome":"UP",...} +---- +// end::accessing-shared-resources-main[] diff --git a/docs/sitegen.yaml b/docs/sitegen.yaml index 28f56d016d9..680296405f1 100644 --- a/docs/sitegen.yaml +++ b/docs/sitegen.yaml @@ -46,6 +46,7 @@ pages: excludes: - "se/grpc/09_marshalling.adoc" - "common/**" + - "shared/**" backend: name: "vuetify" homePage: "about/01_overview.adoc" @@ -237,8 +238,11 @@ backend: type: "icon" value: "share" items: - - includes: + - excludes: + - "mp/cors/hide*.adoc" + includes: - "mp/cors/*.adoc" + - title: "gRPC server" pathprefix: "/mp/grpc" glyph: diff --git a/examples/cors/src/main/java/io/helidon/examples/cors/Main.java b/examples/cors/src/main/java/io/helidon/examples/cors/Main.java index aa807d4362a..c47a4db00a9 100644 --- a/examples/cors/src/main/java/io/helidon/examples/cors/Main.java +++ b/examples/cors/src/main/java/io/helidon/examples/cors/Main.java @@ -24,7 +24,7 @@ import io.helidon.config.Config; import io.helidon.health.HealthSupport; import io.helidon.health.checks.HealthChecks; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.metrics.MetricsSupport; import io.helidon.webserver.Routing; import io.helidon.webserver.WebServer; diff --git a/examples/cors/src/test/java/io/helidon/examples/cors/MainTest.java b/examples/cors/src/test/java/io/helidon/examples/cors/MainTest.java index f1aafeee9de..ef4150980d8 100644 --- a/examples/cors/src/test/java/io/helidon/examples/cors/MainTest.java +++ b/examples/cors/src/test/java/io/helidon/examples/cors/MainTest.java @@ -26,7 +26,7 @@ import io.helidon.common.http.Headers; import io.helidon.common.http.MediaType; import io.helidon.config.Config; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; import io.helidon.webclient.WebClientResponse; diff --git a/examples/dbclient/common/pom.xml b/examples/dbclient/common/pom.xml index 4311899759b..e7ff2d8ed0e 100644 --- a/examples/dbclient/common/pom.xml +++ b/examples/dbclient/common/pom.xml @@ -38,8 +38,8 @@ helidon-webserver - io.helidon.media.jsonp - helidon-media-jsonp-server + io.helidon.media + helidon-media-jsonp diff --git a/examples/dbclient/jdbc/pom.xml b/examples/dbclient/jdbc/pom.xml index 593684f0212..9c12efb34b0 100644 --- a/examples/dbclient/jdbc/pom.xml +++ b/examples/dbclient/jdbc/pom.xml @@ -87,8 +87,8 @@ slf4j-jdk14 - io.helidon.media.jsonb - helidon-media-jsonb-server + io.helidon.media + helidon-media-jsonb io.helidon.config diff --git a/examples/dbclient/jdbc/src/main/java/io/helidon/examples/dbclient/jdbc/JdbcExampleMain.java b/examples/dbclient/jdbc/src/main/java/io/helidon/examples/dbclient/jdbc/JdbcExampleMain.java index c344671b2ef..8fd544ae9f0 100644 --- a/examples/dbclient/jdbc/src/main/java/io/helidon/examples/dbclient/jdbc/JdbcExampleMain.java +++ b/examples/dbclient/jdbc/src/main/java/io/helidon/examples/dbclient/jdbc/JdbcExampleMain.java @@ -23,8 +23,8 @@ import io.helidon.dbclient.DbClient; import io.helidon.dbclient.health.DbClientHealthCheck; import io.helidon.health.HealthSupport; -import io.helidon.media.jsonb.common.JsonbSupport; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonb.JsonbSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.metrics.MetricsSupport; import io.helidon.tracing.TracerBuilder; import io.helidon.webserver.Routing; @@ -97,7 +97,7 @@ static WebServer startServer() throws IOException { private static Routing createRouting(Config config) { Config dbConfig = config.get("db"); - // Interceptors are added through a service loader - see mongoDB example for explicit interceptors + // Client services are added through a service loader - see mongoDB example for explicit services DbClient dbClient = DbClient.builder(dbConfig) .build(); diff --git a/examples/dbclient/jdbc/src/main/java/module-info.java b/examples/dbclient/jdbc/src/main/java/module-info.java index dd2e5b8cfc5..01874b13ee9 100644 --- a/examples/dbclient/jdbc/src/main/java/module-info.java +++ b/examples/dbclient/jdbc/src/main/java/module-info.java @@ -23,8 +23,8 @@ requires io.helidon.config; requires io.helidon.dbclient.health; requires io.helidon.health; - requires io.helidon.media.jsonb.common; - requires io.helidon.media.jsonp.common; + requires io.helidon.media.jsonb; + requires io.helidon.media.jsonp; requires io.helidon.metrics; requires io.helidon.tracing; requires io.helidon.examples.dbclient.common; diff --git a/examples/dbclient/jdbc/src/main/resources/application.yaml b/examples/dbclient/jdbc/src/main/resources/application.yaml index 021ad46b1fa..5714bc11f27 100644 --- a/examples/dbclient/jdbc/src/main/resources/application.yaml +++ b/examples/dbclient/jdbc/src/main/resources/application.yaml @@ -38,36 +38,34 @@ db: enabled: true # name prefix defaults to "db.pool." - if you have more than one client within a JVM, you may want to distinguish between them name-prefix: "hikari." - interceptors: + services: tracing: - global: + # would trace all statement names that start with select- + - statement-names: ["select-.*"] + # would trace all delete statements + - statement-types: ["DELETE"] metrics: - # possible also global: - global: - - type: METER - # changing the name format - by removing the parameter (statement name) we get an overall metric - name-format: "db.meter.overall" - - type: METER - # meter per statement name - - type: METER - # meter per statement type - name-format: "db.meter.%2$s" - named: - - names: ["select-all", "select-one"] - errors: false - type: TIMER - description: "Timer for successful selects" - typed: - - types: ["DELETE", "UPDATE", "INSERT", "DML"] - type: COUNTER - errors: false - name-format: "db.counter.%s.success" - description: "Counter of successful DML statements" - - types: ["DELETE", "UPDATE", "INSERT", "DML"] - type: COUNTER - success: false - name-format: "db.counter.%s.error" - description: "Counter of failed DML statements" + - type: METER + name-format: "db.meter.overall" + - type: METER + # meter per statement name (default name format) + - type: METER + # meter per statement type + name-format: "db.meter.%1$s" + - type: TIMER + errors: false + statement-names: ["select-.*"] + description: "Timer for successful selects" + - type: COUNTER + errors: false + statement-types: ["DELETE", "UPDATE", "INSERT", "DML"] + name-format: "db.counter.%s.success" + description: "Counter of successful DML statements" + - type: COUNTER + statement-types: ["DELETE", "UPDATE", "INSERT", "DML"] + success: false + name-format: "db.counter.%s.error" + description: "Counter of failed DML statements" statements: # required ping statement ping: "DO 0" diff --git a/examples/dbclient/jdbc/src/main/resources/logging.properties b/examples/dbclient/jdbc/src/main/resources/logging.properties index 3120aaf0076..a3b94c39c5f 100644 --- a/examples/dbclient/jdbc/src/main/resources/logging.properties +++ b/examples/dbclient/jdbc/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2019, 2020 Oracle and/or 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,15 +18,14 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=java.util.logging.ConsoleHandler +handlers=io.helidon.common.HelidonConsoleHandler # Global default logging level. Can be overriden by specific handlers and loggers .level=INFO # Helidon Web Server has a custom log formatter that extends SimpleFormatter. # It replaces "!thread!" with the current thread name -java.util.logging.ConsoleHandler.level=INFO -java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter +io.helidon.common.HelidonConsoleHandler.level=ALL 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 #Component specific log levels diff --git a/examples/dbclient/mongodb/pom.xml b/examples/dbclient/mongodb/pom.xml index 33612994702..767529d9496 100644 --- a/examples/dbclient/mongodb/pom.xml +++ b/examples/dbclient/mongodb/pom.xml @@ -79,8 +79,8 @@ helidon-tracing-zipkin - io.helidon.media.jsonb - helidon-media-jsonb-server + io.helidon.media + helidon-media-jsonb io.helidon.config diff --git a/examples/dbclient/mongodb/src/main/java/io/helidon/examples/dbclient/mongo/MongoDbExampleMain.java b/examples/dbclient/mongodb/src/main/java/io/helidon/examples/dbclient/mongo/MongoDbExampleMain.java index 3652a18829a..471d899e1c5 100644 --- a/examples/dbclient/mongodb/src/main/java/io/helidon/examples/dbclient/mongo/MongoDbExampleMain.java +++ b/examples/dbclient/mongodb/src/main/java/io/helidon/examples/dbclient/mongo/MongoDbExampleMain.java @@ -23,12 +23,11 @@ import io.helidon.dbclient.DbClient; import io.helidon.dbclient.DbStatementType; import io.helidon.dbclient.health.DbClientHealthCheck; -import io.helidon.dbclient.metrics.DbCounter; -import io.helidon.dbclient.metrics.DbTimer; +import io.helidon.dbclient.metrics.DbClientMetrics; import io.helidon.dbclient.tracing.DbClientTracing; import io.helidon.health.HealthSupport; -import io.helidon.media.jsonb.common.JsonbSupport; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonb.JsonbSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.metrics.MetricsSupport; import io.helidon.tracing.TracerBuilder; import io.helidon.webserver.Routing; @@ -100,11 +99,12 @@ private static Routing createRouting(Config config) { DbClient dbClient = DbClient.builder(dbConfig) // add an interceptor to named statement(s) - .addInterceptor(DbCounter.create(), "select-all", "select-one") + .addService(DbClientMetrics.counter().statementNames("select-all", "select-one")) // add an interceptor to statement type(s) - .addInterceptor(DbTimer.create(), DbStatementType.DELETE, DbStatementType.UPDATE, DbStatementType.INSERT) + .addService(DbClientMetrics.timer() + .statementTypes(DbStatementType.DELETE, DbStatementType.UPDATE, DbStatementType.INSERT)) // add an interceptor to all statements - .addInterceptor(DbClientTracing.create()) + .addService(DbClientTracing.create()) .build(); HealthSupport health = HealthSupport.builder() diff --git a/examples/dbclient/mongodb/src/main/java/module-info.java b/examples/dbclient/mongodb/src/main/java/module-info.java index 491eec0f6e4..1663f6f2519 100644 --- a/examples/dbclient/mongodb/src/main/java/module-info.java +++ b/examples/dbclient/mongodb/src/main/java/module-info.java @@ -23,8 +23,8 @@ requires io.helidon.config; requires io.helidon.dbclient.health; requires io.helidon.health; - requires io.helidon.media.jsonb.common; - requires io.helidon.media.jsonp.common; + requires io.helidon.media.jsonb; + requires io.helidon.media.jsonp; requires io.helidon.metrics; requires io.helidon.tracing; diff --git a/examples/dbclient/mongodb/src/main/resources/logging.properties b/examples/dbclient/mongodb/src/main/resources/logging.properties index 3120aaf0076..a3b94c39c5f 100644 --- a/examples/dbclient/mongodb/src/main/resources/logging.properties +++ b/examples/dbclient/mongodb/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2019, 2020 Oracle and/or 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,15 +18,14 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=java.util.logging.ConsoleHandler +handlers=io.helidon.common.HelidonConsoleHandler # Global default logging level. Can be overriden by specific handlers and loggers .level=INFO # Helidon Web Server has a custom log formatter that extends SimpleFormatter. # It replaces "!thread!" with the current thread name -java.util.logging.ConsoleHandler.level=INFO -java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter +io.helidon.common.HelidonConsoleHandler.level=ALL 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 #Component specific log levels diff --git a/examples/dbclient/pokemons/pom.xml b/examples/dbclient/pokemons/pom.xml index 5e2ae61f098..40399da2a6f 100644 --- a/examples/dbclient/pokemons/pom.xml +++ b/examples/dbclient/pokemons/pom.xml @@ -96,12 +96,12 @@ helidon-webserver - io.helidon.media.jsonp - helidon-media-jsonp-server + io.helidon.media + helidon-media-jsonp - io.helidon.media.jsonb - helidon-media-jsonb-server + io.helidon.media + helidon-media-jsonb io.helidon.config diff --git a/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonMain.java b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonMain.java index a4701e1ff3f..a205f148f8c 100644 --- a/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonMain.java +++ b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonMain.java @@ -24,8 +24,8 @@ import io.helidon.dbclient.DbClient; import io.helidon.dbclient.health.DbClientHealthCheck; import io.helidon.health.HealthSupport; -import io.helidon.media.jsonb.common.JsonbSupport; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonb.JsonbSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.metrics.MetricsSupport; import io.helidon.tracing.TracerBuilder; import io.helidon.webserver.Routing; @@ -113,7 +113,7 @@ static WebServer startServer() throws IOException { private static Routing createRouting(Config config) { Config dbConfig = config.get("db"); - // Interceptors are added through a service loader - see mongoDB example for explicit interceptors + // Client services are added through a service loader - see mongoDB example for explicit services DbClient dbClient = DbClient.builder(dbConfig) .build(); diff --git a/examples/dbclient/pokemons/src/main/java/module-info.java b/examples/dbclient/pokemons/src/main/java/module-info.java index 51f946625d3..d444c91eb80 100644 --- a/examples/dbclient/pokemons/src/main/java/module-info.java +++ b/examples/dbclient/pokemons/src/main/java/module-info.java @@ -24,8 +24,8 @@ requires io.helidon.config; requires io.helidon.dbclient.health; requires io.helidon.health; - requires io.helidon.media.jsonb.common; - requires io.helidon.media.jsonp.common; + requires io.helidon.media.jsonb; + requires io.helidon.media.jsonp; requires io.helidon.metrics; requires io.helidon.tracing; requires io.helidon.dbclient; diff --git a/examples/dbclient/pokemons/src/main/resources/application.yaml b/examples/dbclient/pokemons/src/main/resources/application.yaml index 583f9fd9d80..b5209fcae33 100644 --- a/examples/dbclient/pokemons/src/main/resources/application.yaml +++ b/examples/dbclient/pokemons/src/main/resources/application.yaml @@ -38,12 +38,11 @@ db: enabled: true # name prefix defaults to "db.pool." - if you have more than one client within a JVM, you may want to distinguish between them name-prefix: "hikari." - interceptors: + services: tracing: - global: + - enabled: true metrics: - global: - - type: METER + - type: METER statements: ## Ping statement ping: "DO 0" diff --git a/examples/dbclient/pokemons/src/main/resources/logging.properties b/examples/dbclient/pokemons/src/main/resources/logging.properties index 3120aaf0076..a3b94c39c5f 100644 --- a/examples/dbclient/pokemons/src/main/resources/logging.properties +++ b/examples/dbclient/pokemons/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2019, 2020 Oracle and/or 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,15 +18,14 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=java.util.logging.ConsoleHandler +handlers=io.helidon.common.HelidonConsoleHandler # Global default logging level. Can be overriden by specific handlers and loggers .level=INFO # Helidon Web Server has a custom log formatter that extends SimpleFormatter. # It replaces "!thread!" with the current thread name -java.util.logging.ConsoleHandler.level=INFO -java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter +io.helidon.common.HelidonConsoleHandler.level=ALL 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 #Component specific log levels diff --git a/examples/dbclient/pokemons/src/main/resources/mongo.yaml b/examples/dbclient/pokemons/src/main/resources/mongo.yaml index 282dd39e77e..a6d11c83c59 100644 --- a/examples/dbclient/pokemons/src/main/resources/mongo.yaml +++ b/examples/dbclient/pokemons/src/main/resources/mongo.yaml @@ -21,19 +21,18 @@ server: print-details: true tracing: - service: jdbc-db + service: mongo-db # docker run --rm --name mongo -p 27017:27017 mongo db: source: "mongoDb" connection: url: "mongodb://127.0.0.1:27017/pokemon" - interceptors: + services: tracing: - global: + - enabled: true metrics: - global: - - type: METER + - type: METER statements: ## Ping statement ping: '{ diff --git a/examples/employee-app/pom.xml b/examples/employee-app/pom.xml index 2cff7c3da12..66d53b76bd7 100644 --- a/examples/employee-app/pom.xml +++ b/examples/employee-app/pom.xml @@ -46,8 +46,8 @@ helidon-webserver - io.helidon.media.jsonb - helidon-media-jsonb-server + io.helidon.media + helidon-media-jsonb io.helidon.config diff --git a/examples/employee-app/src/main/java/io/helidon/service/employee/Main.java b/examples/employee-app/src/main/java/io/helidon/service/employee/Main.java index 920a529e6a1..a325dd342a2 100644 --- a/examples/employee-app/src/main/java/io/helidon/service/employee/Main.java +++ b/examples/employee-app/src/main/java/io/helidon/service/employee/Main.java @@ -23,7 +23,7 @@ import io.helidon.config.Config; import io.helidon.health.HealthSupport; import io.helidon.health.checks.HealthChecks; -import io.helidon.media.jsonb.server.JsonBindingSupport; +import io.helidon.media.jsonb.JsonbSupport; import io.helidon.metrics.MetricsSupport; import io.helidon.webserver.Routing; import io.helidon.webserver.StaticContentSupport; @@ -64,8 +64,11 @@ static WebServer startServer() throws IOException { // By default this will pick up application.yaml from the classpath Config config = Config.create(); - // Get webserver config from the "server" section of application.yaml - WebServer server = WebServer.create(createRouting(config), config.get("server")); + // Get webserver config from the "server" section of application.yaml and JSON support registration + WebServer server = WebServer.builder(createRouting(config)) + .config(config.get("server")) + .addMediaSupport(JsonbSupport.create()) + .build(); // Try to start the server. If successful, print some info and arrange to // print a message at shutdown. If unsuccessful, print the exception. @@ -87,8 +90,9 @@ static WebServer startServer() throws IOException { /** * Creates new {@link Routing}. - * @return routing configured with JSON support, a health check, and a service + * * @param config configuration of this server + * @return routing configured with a health check, and a service */ private static Routing createRouting(Config config) { @@ -100,7 +104,6 @@ private static Routing createRouting(Config config) { return Routing.builder() .register("/public", StaticContentSupport.builder("public") .welcomeFileName("index.html")) - .register(JsonBindingSupport.create()) .register(health) // Health at "/health" .register(metrics) // Metrics at "/metrics" .register("/employees", employeeService) diff --git a/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/AsyncClient.java b/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/AsyncClient.java index 3cfa33d1ae1..2733f9bad66 100644 --- a/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/AsyncClient.java +++ b/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/AsyncClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import javax.inject.Inject; import io.helidon.microprofile.grpc.client.GrpcChannel; -import io.helidon.microprofile.grpc.client.GrpcServiceProxy; +import io.helidon.microprofile.grpc.client.GrpcProxy; /** * A client to the {@link io.helidon.microprofile.grpc.example.client.AsyncStringService}. @@ -42,7 +42,7 @@ public class AsyncClient { * This proxy will connect to the service using the default {@link io.grpc.Channel}. */ @Inject - @GrpcServiceProxy + @GrpcProxy @GrpcChannel(name = "test-server") private AsyncStringService stringService; diff --git a/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/AsyncStringService.java b/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/AsyncStringService.java index 556b153d16a..6c63752839b 100644 --- a/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/AsyncStringService.java +++ b/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/AsyncStringService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import io.helidon.microprofile.grpc.core.Bidirectional; import io.helidon.microprofile.grpc.core.ClientStreaming; -import io.helidon.microprofile.grpc.core.RpcService; +import io.helidon.microprofile.grpc.core.Grpc; import io.helidon.microprofile.grpc.core.ServerStreaming; import io.helidon.microprofile.grpc.core.Unary; @@ -30,10 +30,10 @@ /** * The gRPC StringService. *

- * This class has the {@link io.helidon.microprofile.grpc.core.RpcService} annotation + * This class has the {@link io.helidon.microprofile.grpc.core.Grpc} annotation * so that it will be discovered and loaded using CDI when the MP gRPC server starts. */ -@RpcService +@Grpc @SuppressWarnings("CdiManagedBeanInconsistencyInspection") public interface AsyncStringService { diff --git a/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/Client.java b/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/Client.java index 5993d1e8c14..e6ea6702d83 100644 --- a/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/Client.java +++ b/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/Client.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import javax.inject.Inject; import io.helidon.microprofile.grpc.client.GrpcChannel; -import io.helidon.microprofile.grpc.client.GrpcServiceProxy; +import io.helidon.microprofile.grpc.client.GrpcProxy; import io.grpc.stub.StreamObserver; @@ -46,7 +46,7 @@ public class Client { * This proxy will connect to the service using the default {@link io.grpc.Channel}. */ @Inject - @GrpcServiceProxy + @GrpcProxy @GrpcChannel(name = "test-server") private StringService stringService; diff --git a/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/StringService.java b/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/StringService.java index 7a1b812e4e9..f9de569b7da 100644 --- a/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/StringService.java +++ b/examples/grpc/microprofile/basic-client/src/main/java/io/helidon/microprofile/grpc/example/client/StringService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import io.helidon.microprofile.grpc.core.Bidirectional; import io.helidon.microprofile.grpc.core.ClientStreaming; -import io.helidon.microprofile.grpc.core.RpcService; +import io.helidon.microprofile.grpc.core.Grpc; import io.helidon.microprofile.grpc.core.ServerStreaming; import io.helidon.microprofile.grpc.core.Unary; @@ -29,10 +29,10 @@ /** * The gRPC StringService. *

- * This class has the {@link io.helidon.microprofile.grpc.core.RpcService} annotation + * This class has the {@link io.helidon.microprofile.grpc.core.Grpc} annotation * so that it will be discovered and loaded using CDI when the MP gRPC server starts. */ -@RpcService +@Grpc @SuppressWarnings("CdiManagedBeanInconsistencyInspection") public interface StringService { diff --git a/examples/grpc/microprofile/basic-server-implicit/README.md b/examples/grpc/microprofile/basic-server-implicit/README.md index 62714d3f619..75970edcbad 100644 --- a/examples/grpc/microprofile/basic-server-implicit/README.md +++ b/examples/grpc/microprofile/basic-server-implicit/README.md @@ -7,7 +7,7 @@ It is implicit because in this example you don't write the The gRPC services to deploy will be discovered by CDI when the gRPC server starts. The `StringService` is a POJO service implementation that is annotated with the -CDI qualifier `RpcService` so that it can be discovered. +CDI qualifier `Grpc` so that it can be discovered. Two additional services (`GreetService` and `EchoService`) that are not normally CDI managed beans are manually added as CDI managed beans in the `AdditionalServices` class diff --git a/examples/grpc/microprofile/basic-server-implicit/src/main/java/io/helidon/microprofile/grpc/example/basic/implicit/AsyncStringService.java b/examples/grpc/microprofile/basic-server-implicit/src/main/java/io/helidon/microprofile/grpc/example/basic/implicit/AsyncStringService.java index c3e1c5e57dd..5ffb7e80ea5 100644 --- a/examples/grpc/microprofile/basic-server-implicit/src/main/java/io/helidon/microprofile/grpc/example/basic/implicit/AsyncStringService.java +++ b/examples/grpc/microprofile/basic-server-implicit/src/main/java/io/helidon/microprofile/grpc/example/basic/implicit/AsyncStringService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import io.helidon.grpc.server.CollectingObserver; import io.helidon.microprofile.grpc.core.Bidirectional; import io.helidon.microprofile.grpc.core.ClientStreaming; -import io.helidon.microprofile.grpc.core.RpcService; +import io.helidon.microprofile.grpc.core.Grpc; import io.helidon.microprofile.grpc.core.ServerStreaming; import io.helidon.microprofile.grpc.core.Unary; @@ -35,11 +35,11 @@ /** * The gRPC StringService implementation that uses async unary methods. *

- * This class is a gRPC service annotated with {@link io.helidon.microprofile.grpc.core.RpcService} and + * This class is a gRPC service annotated with {@link io.helidon.microprofile.grpc.core.Grpc} and * {@link javax.enterprise.context.ApplicationScoped} so that it will be discovered and deployed using * CDI when the MP gRPC server starts. */ -@RpcService +@Grpc @ApplicationScoped public class AsyncStringService { diff --git a/examples/grpc/microprofile/basic-server-implicit/src/main/java/io/helidon/microprofile/grpc/example/basic/implicit/StringService.java b/examples/grpc/microprofile/basic-server-implicit/src/main/java/io/helidon/microprofile/grpc/example/basic/implicit/StringService.java index f27a0950849..d47a2a9f068 100644 --- a/examples/grpc/microprofile/basic-server-implicit/src/main/java/io/helidon/microprofile/grpc/example/basic/implicit/StringService.java +++ b/examples/grpc/microprofile/basic-server-implicit/src/main/java/io/helidon/microprofile/grpc/example/basic/implicit/StringService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import io.helidon.grpc.server.CollectingObserver; import io.helidon.microprofile.grpc.core.Bidirectional; import io.helidon.microprofile.grpc.core.ClientStreaming; -import io.helidon.microprofile.grpc.core.RpcService; +import io.helidon.microprofile.grpc.core.Grpc; import io.helidon.microprofile.grpc.core.ServerStreaming; import io.helidon.microprofile.grpc.core.Unary; @@ -33,10 +33,10 @@ /** * The gRPC StringService implementation. *

- * This class is a gRPC service annotated with {@link RpcService} and {@link ApplicationScoped} + * This class is a gRPC service annotated with {@link Grpc} and {@link ApplicationScoped} * so that it will be discovered and deployed using CDI when the MP gRPC server starts. */ -@RpcService +@Grpc @ApplicationScoped public class StringService { diff --git a/examples/grpc/microprofile/metrics/src/main/java/io/helidon/microprofile/grpc/example/metrics/StringService.java b/examples/grpc/microprofile/metrics/src/main/java/io/helidon/microprofile/grpc/example/metrics/StringService.java index 114af01cf29..75b07e49b0d 100644 --- a/examples/grpc/microprofile/metrics/src/main/java/io/helidon/microprofile/grpc/example/metrics/StringService.java +++ b/examples/grpc/microprofile/metrics/src/main/java/io/helidon/microprofile/grpc/example/metrics/StringService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import io.helidon.grpc.server.CollectingObserver; import io.helidon.microprofile.grpc.core.Bidirectional; import io.helidon.microprofile.grpc.core.ClientStreaming; -import io.helidon.microprofile.grpc.core.RpcService; +import io.helidon.microprofile.grpc.core.Grpc; import io.helidon.microprofile.grpc.core.ServerStreaming; import io.helidon.microprofile.grpc.core.Unary; @@ -36,11 +36,11 @@ /** * The gRPC StringService implementation. *

- * This class is a gRPC service annotated with {@link io.helidon.microprofile.grpc.core.RpcService} + * This class is a gRPC service annotated with {@link io.helidon.microprofile.grpc.core.Grpc} * and {@link javax.enterprise.context.ApplicationScoped} so that it will be discovered and deployed * using CDI when the MP gRPC server starts. */ -@RpcService +@Grpc @ApplicationScoped public class StringService { diff --git a/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AtnProvider.java b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AtnProvider.java index eada2adeaa4..0b2e8a27ecb 100644 --- a/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AtnProvider.java +++ b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AtnProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -99,12 +99,13 @@ private List fromConfig(List configList) { } private List fromAnnotations(EndpointConfig endpointConfig) { - return endpointConfig.combineAnnotations(Authentications.class, EndpointConfig.AnnotationScope.METHOD) - .stream() - .map(Authentications::value) - .flatMap(Arrays::stream) - .map(Auth::new) - .collect(Collectors.toList()); + return endpointConfig.securityLevels() + .stream() + .flatMap(level -> level.combineAnnotations(Authentications.class, EndpointConfig.AnnotationScope.METHOD).stream()) + .map(Authentications::value) + .flatMap(Arrays::stream) + .map(Auth::new) + .collect(Collectors.toList()); } private Subject buildSubject(Auth authentication) { diff --git a/examples/media/multipart/pom.xml b/examples/media/multipart/pom.xml index 53691011e31..6ea6b4a5107 100644 --- a/examples/media/multipart/pom.xml +++ b/examples/media/multipart/pom.xml @@ -49,8 +49,8 @@ helidon-media-multipart - helidon-media-jsonp-common - io.helidon.media.jsonp + io.helidon.media + helidon-media-jsonp org.glassfish diff --git a/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/FileService.java b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/FileService.java index af7cb928792..f473ae2ef43 100644 --- a/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/FileService.java +++ b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/FileService.java @@ -16,6 +16,7 @@ package io.helidon.examples.media.multipart; import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.file.Files; import java.nio.file.Path; @@ -125,7 +126,7 @@ private void streamUpload(ServerRequest req, ServerResponse res) { }).forEach((part) -> { if ("file[]".equals(part.name())) { final ByteChannel channel = newByteChannel(storage, part.filename()); - Multi.from(part.content()).forEach(chunk -> writeChunk(channel, chunk)); + Multi.create(part.content()).forEach(chunk -> writeChunk(channel, chunk)); } }); } @@ -162,7 +163,9 @@ private static void writeBytes(Path storage, String fname, byte[] bytes) { private static void writeChunk(ByteChannel channel, DataChunk chunk) { try { - channel.write(chunk.data()); + for (ByteBuffer byteBuffer : chunk.data()) { + channel.write(byteBuffer); + } } catch (IOException ex) { throw new RuntimeException(ex); } finally { diff --git a/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/Main.java b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/Main.java index a238ee41741..d85b6b1a466 100644 --- a/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/Main.java +++ b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/Main.java @@ -16,7 +16,7 @@ package io.helidon.examples.media.multipart; import io.helidon.common.http.Http; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.media.multipart.MultiPartSupport; import io.helidon.webserver.Routing; import io.helidon.webserver.ServerConfiguration; diff --git a/examples/microprofile/cors/src/test/java/io/helidon/microprofile/examples/cors/TestCORS.java b/examples/microprofile/cors/src/test/java/io/helidon/microprofile/examples/cors/TestCORS.java index 2468a3fec3d..75fb68abb06 100644 --- a/examples/microprofile/cors/src/test/java/io/helidon/microprofile/examples/cors/TestCORS.java +++ b/examples/microprofile/cors/src/test/java/io/helidon/microprofile/examples/cors/TestCORS.java @@ -29,7 +29,7 @@ import io.helidon.common.http.Headers; import io.helidon.common.http.MediaType; import io.helidon.config.Config; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.microprofile.server.Server; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; diff --git a/examples/openapi/src/main/java/io/helidon/examples/openapi/Main.java b/examples/openapi/src/main/java/io/helidon/examples/openapi/Main.java index d8a626640ae..d739e25e99f 100644 --- a/examples/openapi/src/main/java/io/helidon/examples/openapi/Main.java +++ b/examples/openapi/src/main/java/io/helidon/examples/openapi/Main.java @@ -22,7 +22,7 @@ import io.helidon.config.Config; import io.helidon.health.HealthSupport; import io.helidon.health.checks.HealthChecks; -import io.helidon.media.jsonp.server.JsonSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.metrics.MetricsSupport; import io.helidon.openapi.OpenAPISupport; import io.helidon.webserver.Routing; @@ -62,8 +62,11 @@ static WebServer startServer() throws IOException { // By default this will pick up application.yaml from the classpath Config config = Config.create(); - // Get webserver config from the "server" section of application.yaml - WebServer server = WebServer.create(createRouting(config), config.get("server")); + // Get webserver config from the "server" section of application.yaml and register JSON support + WebServer server = WebServer.builder(createRouting(config)) + .config(config.get("server")) + .addMediaSupport(JsonpSupport.create()) + .build(); // Try to start the server. If successful, print some info and arrange to // print a message at shutdown. If unsuccessful, print the exception. @@ -88,7 +91,7 @@ static WebServer startServer() throws IOException { /** * Creates new {@link Routing}. * - * @return routing configured with JSON support, a health check, and a service + * @return routing configured with a health check, and a service * @param config configuration of this server */ private static Routing createRouting(Config config) { @@ -100,7 +103,6 @@ private static Routing createRouting(Config config) { .build(); return Routing.builder() - .register(JsonSupport.create()) .register(OpenAPISupport.create(config.get(OpenAPISupport.Builder.CONFIG_KEY))) .register(health) // Health at "/health" .register(metrics) // Metrics at "/metrics" diff --git a/examples/openapi/src/test/java/io/helidon/examples/openapi/MainTest.java b/examples/openapi/src/test/java/io/helidon/examples/openapi/MainTest.java index cd3241dfd3c..dae767f53ac 100644 --- a/examples/openapi/src/test/java/io/helidon/examples/openapi/MainTest.java +++ b/examples/openapi/src/test/java/io/helidon/examples/openapi/MainTest.java @@ -27,7 +27,7 @@ import io.helidon.common.http.MediaType; import io.helidon.examples.openapi.internal.SimpleAPIModelReader; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; import io.helidon.webserver.WebServer; diff --git a/examples/quickstarts/helidon-quickstart-se/pom.xml b/examples/quickstarts/helidon-quickstart-se/pom.xml index e01b60b7457..0af94db64b4 100644 --- a/examples/quickstarts/helidon-quickstart-se/pom.xml +++ b/examples/quickstarts/helidon-quickstart-se/pom.xml @@ -42,8 +42,8 @@ helidon-webserver - io.helidon.media.jsonp - helidon-media-jsonp-common + io.helidon.media + helidon-media-jsonp io.helidon.config diff --git a/examples/quickstarts/helidon-quickstart-se/src/main/java/io/helidon/examples/quickstart/se/Main.java b/examples/quickstarts/helidon-quickstart-se/src/main/java/io/helidon/examples/quickstart/se/Main.java index 2a86143e423..396bfc200bc 100644 --- a/examples/quickstarts/helidon-quickstart-se/src/main/java/io/helidon/examples/quickstart/se/Main.java +++ b/examples/quickstarts/helidon-quickstart-se/src/main/java/io/helidon/examples/quickstart/se/Main.java @@ -23,7 +23,7 @@ import io.helidon.config.Config; import io.helidon.health.HealthSupport; import io.helidon.health.checks.HealthChecks; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.metrics.MetricsSupport; import io.helidon.webserver.Routing; import io.helidon.webserver.WebServer; diff --git a/examples/quickstarts/helidon-quickstart-se/src/test/java/io/helidon/examples/quickstart/se/MainTest.java b/examples/quickstarts/helidon-quickstart-se/src/test/java/io/helidon/examples/quickstart/se/MainTest.java index d85dde19dd6..62204ee7bc2 100644 --- a/examples/quickstarts/helidon-quickstart-se/src/test/java/io/helidon/examples/quickstart/se/MainTest.java +++ b/examples/quickstarts/helidon-quickstart-se/src/test/java/io/helidon/examples/quickstart/se/MainTest.java @@ -23,7 +23,7 @@ import javax.json.JsonBuilderFactory; import javax.json.JsonObject; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; import io.helidon.webserver.WebServer; diff --git a/examples/quickstarts/helidon-standalone-quickstart-se/src/main/java/io/helidon/examples/quickstart/se/Main.java b/examples/quickstarts/helidon-standalone-quickstart-se/src/main/java/io/helidon/examples/quickstart/se/Main.java index 173826f38a0..f5ef5950015 100644 --- a/examples/quickstarts/helidon-standalone-quickstart-se/src/main/java/io/helidon/examples/quickstart/se/Main.java +++ b/examples/quickstarts/helidon-standalone-quickstart-se/src/main/java/io/helidon/examples/quickstart/se/Main.java @@ -23,7 +23,7 @@ import io.helidon.config.Config; import io.helidon.health.HealthSupport; import io.helidon.health.checks.HealthChecks; -import io.helidon.media.jsonp.server.JsonSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.metrics.MetricsSupport; import io.helidon.webserver.Routing; import io.helidon.webserver.WebServer; @@ -61,8 +61,11 @@ static WebServer startServer() throws IOException { // By default this will pick up application.yaml from the classpath Config config = Config.create(); - // Get webserver config from the "server" section of application.yaml - WebServer server = WebServer.create(createRouting(config), config.get("server")); + // Get webserver config from the "server" section of application.yaml and register JSON support + WebServer server = WebServer.builder(createRouting(config)) + .config(config.get("server")) + .addMediaSupport(JsonpSupport.create()) + .build(); // Try to start the server. If successful, print some info and arrange to // print a message at shutdown. If unsuccessful, print the exception. @@ -87,7 +90,7 @@ static WebServer startServer() throws IOException { /** * Creates new {@link Routing}. * - * @return routing configured with JSON support, a health check, and a service + * @return routing configured with a health check, and a service * @param config configuration of this server */ private static Routing createRouting(Config config) { @@ -99,7 +102,6 @@ private static Routing createRouting(Config config) { .build(); return Routing.builder() - .register(JsonSupport.create()) .register(health) // Health at "/health" .register(metrics) // Metrics at "/metrics" .register("/greet", greetService) diff --git a/examples/quickstarts/helidon-standalone-quickstart-se/src/test/java/io/helidon/examples/quickstart/se/MainTest.java b/examples/quickstarts/helidon-standalone-quickstart-se/src/test/java/io/helidon/examples/quickstart/se/MainTest.java index 179f4c64b54..4913287390e 100644 --- a/examples/quickstarts/helidon-standalone-quickstart-se/src/test/java/io/helidon/examples/quickstart/se/MainTest.java +++ b/examples/quickstarts/helidon-standalone-quickstart-se/src/test/java/io/helidon/examples/quickstart/se/MainTest.java @@ -23,7 +23,7 @@ import javax.json.JsonObject; import javax.json.JsonReaderFactory; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; import io.helidon.webserver.WebServer; diff --git a/examples/security/attribute-based-access-control/src/main/java/module-info.java b/examples/security/attribute-based-access-control/src/main/java/module-info.java index 3a9af83c085..d8f2190c3ee 100644 --- a/examples/security/attribute-based-access-control/src/main/java/module-info.java +++ b/examples/security/attribute-based-access-control/src/main/java/module-info.java @@ -31,9 +31,6 @@ requires io.helidon.security.abac.policy; requires io.helidon.security.abac.scope; - // needed for jersey to start without a lot of errors (hk2 actually) - requires java.xml.bind; - // java util logging requires java.logging; diff --git a/examples/security/idcs-login/pom.xml b/examples/security/idcs-login/pom.xml index 524184f816e..6c570f04b58 100644 --- a/examples/security/idcs-login/pom.xml +++ b/examples/security/idcs-login/pom.xml @@ -45,8 +45,8 @@ helidon-webserver - io.helidon.media.jsonp - helidon-media-jsonp-server + io.helidon.media + helidon-media-jsonp diff --git a/examples/security/spi-examples/src/test/java/io/helidon/security/examples/spi/AtnProviderSyncTest.java b/examples/security/spi-examples/src/test/java/io/helidon/security/examples/spi/AtnProviderSyncTest.java index d4f7d18d00e..7bc45249f14 100644 --- a/examples/security/spi-examples/src/test/java/io/helidon/security/examples/spi/AtnProviderSyncTest.java +++ b/examples/security/spi-examples/src/test/java/io/helidon/security/examples/spi/AtnProviderSyncTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import io.helidon.security.Security; import io.helidon.security.SecurityContext; import io.helidon.security.SecurityEnvironment; +import io.helidon.security.SecurityLevel; import io.helidon.security.SecurityResponse; import io.helidon.security.Subject; @@ -93,9 +94,13 @@ public Class annotationType() { when(context.service()).thenReturn(Optional.empty()); SecurityEnvironment se = SecurityEnvironment.create(); + + SecurityLevel level = SecurityLevel.create("mock") + .withClassAnnotations(Map.of(AtnProviderSync.AtnAnnot.class, List.of(annot))) + .build(); + EndpointConfig ep = EndpointConfig.builder() - .annotations(EndpointConfig.AnnotationScope.CLASS, - Map.of(AtnProviderSync.AtnAnnot.class, List.of(annot))) + .securityLevels(List.of(level)) .build(); ProviderRequest request = mock(ProviderRequest.class); diff --git a/examples/security/webserver-digest-auth/pom.xml b/examples/security/webserver-digest-auth/pom.xml index c0036f7039f..03cdfed7cbb 100644 --- a/examples/security/webserver-digest-auth/pom.xml +++ b/examples/security/webserver-digest-auth/pom.xml @@ -1,7 +1,7 @@ - - 4.0.0 - - io.helidon.integrations.cdi - helidon-integrations-cdi-project - 2.0.0-SNAPSHOT - - helidon-integrations-cdi-jpa-weld - Helidon CDI Integrations JPA Weld - - - - - package - - - - - - org.junit.jupiter - junit-jupiter-api - test - - - org.hamcrest - hamcrest-all - test - - - io.helidon.microprofile.cdi - helidon-microprofile-cdi - test - - - org.slf4j - slf4j-jdk14 - test - - - com.h2database - h2 - test - - - ${project.groupId} - helidon-integrations-cdi-jta-weld - test - - - ${project.groupId} - helidon-integrations-cdi-datasource-hikaricp - test - - - ${project.groupId} - helidon-integrations-cdi-eclipselink - test - - - io.helidon.microprofile.config - helidon-microprofile-config - test - - - - - ${project.groupId} - helidon-integrations-cdi-jpa - runtime - - - org.jboss - jandex - runtime - - - - - jakarta.annotation - jakarta.annotation-api - provided - - - jakarta.enterprise - jakarta.enterprise.cdi-api - provided - - - jakarta.persistence - jakarta.persistence-api - provided - - - jakarta.transaction - jakarta.transaction-api - provided - - - jakarta.validation - jakarta.validation-api - provided - - - - - org.jboss.weld - weld-spi - compile - - - - - - - maven-surefire-plugin - - - ${basedir}/src/test/java/logging.properties - - - - - org.jboss.jandex - jandex-maven-plugin - - - make-index - - jandex - - process-classes - - - - - - - diff --git a/integrations/cdi/jpa-weld/src/main/java/io/helidon/integrations/cdi/jpa/weld/WeldJpaInjectionServices.java b/integrations/cdi/jpa-weld/src/main/java/io/helidon/integrations/cdi/jpa/weld/WeldJpaInjectionServices.java deleted file mode 100644 index 571f0f18515..00000000000 --- a/integrations/cdi/jpa-weld/src/main/java/io/helidon/integrations/cdi/jpa/weld/WeldJpaInjectionServices.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.integrations.cdi.jpa.weld; - -import java.util.ResourceBundle; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.enterprise.inject.spi.InjectionPoint; -import javax.persistence.EntityManager; -import javax.persistence.EntityManagerFactory; - -import org.jboss.weld.injection.spi.JpaInjectionServices; -import org.jboss.weld.injection.spi.ResourceReferenceFactory; - -/** - * A {@link JpaInjectionServices} implementation that integrates JPA - * functionality into Weld-based CDI environments. - * - * @see JpaInjectionServices - * - * @deprecated This extension is no longer needed and is slated for - * removal. - */ -@Deprecated -final class WeldJpaInjectionServices implements JpaInjectionServices { - - - /* - * Static fields. - */ - - - /** - * The {@link Logger} for use by all instances of this class. - * - *

This field is never {@code null}.

- */ - private static final Logger LOGGER = Logger.getLogger(WeldJpaInjectionServices.class.getName(), - WeldJpaInjectionServices.class.getPackage().getName() + ".Messages"); - - - /* - * Constructors. - */ - - - /** - * Creates a new {@link WeldJpaInjectionServices}. - * - *

Oddly, the fact that this constructor is {@code private} - * does not prevent Weld from loading it as a service. This is an - * unexpected bonus as nothing about this class should be {@code - * public}.

- */ - WeldJpaInjectionServices() { - super(); - } - - /** - * Throws an {@link IllegalArgumentException} when invoked. - * - * @exception IllegalArgumentException when invoked - * - * @see ResourceReferenceFactory#createResource() - * - * @deprecated This class is deprecated, no longer needed and is - * slated for removal. - */ - @Deprecated - @Override - public ResourceReferenceFactory registerPersistenceContextInjectionPoint(final InjectionPoint injectionPoint) { - final String cn = WeldJpaInjectionServices.class.getName(); - final String mn = "registerPersistenceContextInjectionPoint"; - if (LOGGER.isLoggable(Level.FINER)) { - LOGGER.entering(cn, mn, injectionPoint); - } - final ResourceBundle messages = ResourceBundle.getBundle(this.getClass().getPackage().getName() + ".Messages"); - assert messages != null; - throw new IllegalArgumentException(messages.getString("deprecated")); - } - - /** - * Throws an {@link IllegalArgumentException} when invoked. - * - * @exception IllegalArgumentException when invoked - * - * @see ResourceReferenceFactory#createResource() - * - * @deprecated This class is deprecated, no longer needed and is - * slated for removal. - */ - @Deprecated - @Override - public ResourceReferenceFactory registerPersistenceUnitInjectionPoint(final InjectionPoint ip) { - final String cn = WeldJpaInjectionServices.class.getName(); - final String mn = "registerPersistenceUnitInjectionPoint"; - if (LOGGER.isLoggable(Level.FINER)) { - LOGGER.entering(cn, mn, ip); - } - final ResourceBundle messages = ResourceBundle.getBundle(this.getClass().getPackage().getName() + ".Messages"); - assert messages != null; - throw new IllegalArgumentException(messages.getString("deprecated")); - } - - /** - * Invoked by Weld automatically to clean up any resources held by - * this class. - */ - @Override - public void cleanup() { - - } - - /** - * Throws an {@link IllegalArgumentException} when invoked. - * - * @param injectionPoint ignored - * - * @return nothing - * - * @exception IllegalArgumentException when invoked - * - * @see #registerPersistenceContextInjectionPoint(InjectionPoint)} - * - * @deprecated This class is deprecated, no longer needed and is - * slated for removal. - */ - @Deprecated - public EntityManager resolvePersistenceContext(final InjectionPoint injectionPoint) { - return this.registerPersistenceContextInjectionPoint(injectionPoint).createResource().getInstance(); - } - - /** - * Throws an {@link IllegalArgumentException} when invoked. - * - * @param injectionPoint ignored - * - * @return nothing - * - * @exception IllegalArgumentException when invoked - * - * @see #registerPersistenceContextInjectionPoint(InjectionPoint)} - * - * @deprecated This class is deprecated, no longer needed and is - * slated for removal. - */ - @Deprecated - public EntityManagerFactory resolvePersistenceUnit(final InjectionPoint injectionPoint) { - return this.registerPersistenceUnitInjectionPoint(injectionPoint).createResource().getInstance(); - } - -} diff --git a/integrations/cdi/jpa-weld/src/main/java/io/helidon/integrations/cdi/jpa/weld/package-info.java b/integrations/cdi/jpa-weld/src/main/java/io/helidon/integrations/cdi/jpa/weld/package-info.java deleted file mode 100644 index b93d33e515b..00000000000 --- a/integrations/cdi/jpa-weld/src/main/java/io/helidon/integrations/cdi/jpa/weld/package-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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. - */ - -/** - * Provides classes and interfaces that help to integrate JPA into Weld-based CDI 2.0 SE environments. - * - * @see - * io.helidon.integrations.cdi.jpa.weld.WeldJpaInjectionServices - */ -package io.helidon.integrations.cdi.jpa.weld; diff --git a/integrations/cdi/jpa-weld/src/main/java/module-info.java b/integrations/cdi/jpa-weld/src/main/java/module-info.java deleted file mode 100644 index a1b9b82e432..00000000000 --- a/integrations/cdi/jpa-weld/src/main/java/module-info.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2020 Oracle and/or 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. - */ - -/** - * Provides classes and interfaces that help to integrate JPA into Weld-based CDI 2.0 SE environments. - * - * @see - * io.helidon.integrations.cdi.jpa.weld.WeldJpaInjectionServices - */ -module io.helidon.integrations.cdi.jpa.weld { - requires java.transaction; - requires java.annotation; - requires java.sql; - requires java.persistence; - requires jakarta.interceptor.api; - requires jakarta.inject.api; - requires jakarta.enterprise.cdi.api; - requires weld.spi; - - exports io.helidon.integrations.cdi.jpa.weld; -} diff --git a/integrations/cdi/jpa-weld/src/main/resources/io/helidon/integrations/cdi/jpa/weld/Messages.properties b/integrations/cdi/jpa-weld/src/main/resources/io/helidon/integrations/cdi/jpa/weld/Messages.properties deleted file mode 100644 index 9627ef27dda..00000000000 --- a/integrations/cdi/jpa-weld/src/main/resources/io/helidon/integrations/cdi/jpa/weld/Messages.properties +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. -# -# 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. -# -transactionTypeMismatch=A resource-local EntityManagerFactory was requested for \ - the persistence unit named "{0}", but a JTA \ - EntityManagerFactory, {1}, was previously associated \ - with the persistence unit. -persistenceUnitNameMismatch=The sole PersistenceUnitInfo, {0}, representing \ - the persistence unit with name "{1}", will be used \ - instead of the requested persistence unit named \ - "{2}" -deprecated=This extension is no longer needed and is slated for removal. diff --git a/integrations/cdi/jpa-weld/src/test/java/io/helidon/integrations/cdi/jpa/weld/TestDeprecation.java b/integrations/cdi/jpa-weld/src/test/java/io/helidon/integrations/cdi/jpa/weld/TestDeprecation.java deleted file mode 100644 index 0b7f8e9887c..00000000000 --- a/integrations/cdi/jpa-weld/src/test/java/io/helidon/integrations/cdi/jpa/weld/TestDeprecation.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.integrations.cdi.jpa.weld; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -final class TestDeprecation { - - TestDeprecation() { - super(); - } - - @Test - @Deprecated - void testDeprecation() { - assertThrows(IllegalArgumentException.class, - () -> new WeldJpaInjectionServices().registerPersistenceContextInjectionPoint(null)); - } - -} diff --git a/integrations/cdi/jpa-weld/src/test/java/io/helidon/integrations/cdi/jpa/weld/TestIntegration.java b/integrations/cdi/jpa-weld/src/test/java/io/helidon/integrations/cdi/jpa/weld/TestIntegration.java deleted file mode 100644 index e68d93f555b..00000000000 --- a/integrations/cdi/jpa-weld/src/test/java/io/helidon/integrations/cdi/jpa/weld/TestIntegration.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.integrations.cdi.jpa.weld; - -import javax.annotation.sql.DataSourceDefinition; -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.context.ContextNotActiveException; -import javax.enterprise.context.Initialized; -import javax.enterprise.event.Observes; -import javax.enterprise.inject.se.SeContainer; -import javax.enterprise.inject.se.SeContainerInitializer; -import javax.inject.Inject; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.transaction.Status; -import javax.transaction.SystemException; -import javax.transaction.Transaction; -import javax.transaction.Transactional; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -@ApplicationScoped -@DataSourceDefinition( - name = "test", - className = "org.h2.jdbcx.JdbcDataSource", - url = "jdbc:h2:mem:test", - serverName = "", - properties = { - "user=sa" - } -) -public class TestIntegration { - - private SeContainer cdiContainer; - - @Inject - private Transaction transaction; - - @PersistenceContext - private EntityManager entityManager; - - TestIntegration() { - super(); - } - - @BeforeEach - void startCdiContainer() { - shutDownCdiContainer(); - final SeContainerInitializer initializer = SeContainerInitializer.newInstance() - .addBeanClasses(TestIntegration.class); - assertNotNull(initializer); - this.cdiContainer = initializer.initialize(); - } - - @AfterEach - void shutDownCdiContainer() { - if (this.cdiContainer != null) { - this.cdiContainer.close(); - this.cdiContainer = null; - } - } - - private static void onStartup(@Observes @Initialized(ApplicationScoped.class) final Object event, - final TestIntegration self) - throws SystemException { - assertNotNull(event); - assertNotNull(self); - self.doSomethingTransactional(); - self.doSomethingNonTransactional(); - } - - @Transactional(Transactional.TxType.REQUIRED) - void doSomethingTransactional() throws SystemException { - assertNotNull(this.transaction); - assertEquals(Status.STATUS_ACTIVE, this.transaction.getStatus()); - assertNotNull(this.entityManager); - assertTrue(this.entityManager.isOpen()); - assertTrue(this.entityManager.isJoinedToTransaction()); - } - - void doSomethingNonTransactional() { - assertNotNull(this.transaction); // ...but the scope won't be active - try { - this.transaction.toString(); - fail("The TransactionScoped scope was active when it should not have been"); - } catch (final ContextNotActiveException expected) { - - } - assertNotNull(this.entityManager); - assertTrue(this.entityManager.isOpen()); - - } - - @Test - void testIntegration() { - } - -} diff --git a/integrations/cdi/jpa-weld/src/test/java/logging.properties b/integrations/cdi/jpa-weld/src/test/java/logging.properties deleted file mode 100644 index 4b5886c1eeb..00000000000 --- a/integrations/cdi/jpa-weld/src/test/java/logging.properties +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. -# -# 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. -# -.level=WARNING -com.arjuna.level=WARNING -com.zaxxer.hikari.level=WARNING -handlers=io.helidon.common.HelidonConsoleHandler -io.helidon.integrations.cdi.jpa.level=WARNING -org.eclipse.persistence.level=WARNING -org.jboss.weld.level=WARNING diff --git a/integrations/cdi/jpa-weld/src/test/resources/META-INF/persistence.xml b/integrations/cdi/jpa-weld/src/test/resources/META-INF/persistence.xml deleted file mode 100644 index afb0d0093f7..00000000000 --- a/integrations/cdi/jpa-weld/src/test/resources/META-INF/persistence.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - test - - - - - - - - - - - - - - - - diff --git a/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/MicroProfileConfigAuthenticationDetailsProvider.java b/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/MicroProfileConfigAuthenticationDetailsProvider.java index bb2d4e318ad..fa26c2575c8 100644 --- a/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/MicroProfileConfigAuthenticationDetailsProvider.java +++ b/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/MicroProfileConfigAuthenticationDetailsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or 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,6 +36,12 @@ final class MicroProfileConfigAuthenticationDetailsProvider extends CustomerAuth this.config = Objects.requireNonNull(config); } + /** + * This method is implementing an interface of a third party. Until + * that interface removes this method, we need to keep it in Helidon. + * + * @return pass phrase + */ @Deprecated @Override public String getPassPhrase() { diff --git a/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/OciConfigConfigSource.java b/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/OciConfigConfigSource.java index 49ec4818f5c..cd282ff73fd 100644 --- a/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/OciConfigConfigSource.java +++ b/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/OciConfigConfigSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider; import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.eclipse.microprofile.config.spi.ConfigSource; /** @@ -98,15 +98,18 @@ public int getOrdinal() { @Override public String getValue(final String propertyName) { final String returnValue; - if (propertyName == null || propertyName.equals("oci.auth.profile") || propertyName.equals("oci.config.path")) { + if (propertyName == null) { returnValue = null; + } else if (propertyName.equals(ConfigSource.CONFIG_ORDINAL)) { + returnValue = String.valueOf(this.getOrdinal()); } else { Map properties = this.properties; if (properties == null) { - final Config config = ConfigProvider.getConfig(); - assert config != null; + final Config config = ConfigProviderResolver.instance() + .getBuilder() + .addDefaultSources() + .build(); final String profile = config.getOptionalValue("oci.auth.profile", String.class).orElse("DEFAULT"); - assert profile != null; final String configFilePath = config.getOptionalValue("oci.config.path", String.class).orElse(null); final ConfigFileAuthenticationDetailsProvider provider; ConfigFileAuthenticationDetailsProvider temp = null; @@ -122,10 +125,8 @@ public String getValue(final String propertyName) { provider = temp; } properties = createProperties(provider); - assert properties != null; this.properties = properties; } - assert properties != null; returnValue = properties.get(propertyName); } return returnValue; @@ -176,7 +177,7 @@ public String getValue(final String propertyName) { */ @Override public Map getProperties() { - Map properties = this.properties; + final Map properties = this.properties; return properties == null || properties.isEmpty() ? Collections.emptyMap() : properties; } diff --git a/integrations/cdi/pom.xml b/integrations/cdi/pom.xml index 84d7fdb63f6..b6986d7dc78 100644 --- a/integrations/cdi/pom.xml +++ b/integrations/cdi/pom.xml @@ -1,7 +1,7 @@ jta-cdi jta-weld oci-objectstorage-cdi diff --git a/integrations/graal/native-image-extension/src/main/resources/META-INF/native-image/helidon/reflection-config.json b/integrations/graal/native-image-extension/src/main/resources/META-INF/native-image/helidon/reflection-config.json deleted file mode 100644 index bbf3efb63c9..00000000000 --- a/integrations/graal/native-image-extension/src/main/resources/META-INF/native-image/helidon/reflection-config.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "annotated":[ - "javax.inject.Singleton", - "org.jvnet.hk2.annotations.Service", - "org.jvnet.hk2.annotations.Contract", - "org.glassfish.hk2.api.PerLookup", - "org.glassfish.jersey.process.internal.RequestScoped", - "org.glassfish.jersey.spi.Contract", - "org.glassfish.jersey.internal.inject.PerLookup" - ], - "class-hierarchy": [ - "java.util.logging.Formatter", - "javax.ws.rs.ext.ContextResolver", - "javax.ws.rs.ext.ExceptionMapper", - "javax.ws.rs.ext.MessageBodyReader", - "javax.ws.rs.ext.MessageBodyWriter", - "javax.ws.rs.ext.ReaderInterceptor", - "javax.ws.rs.ext.WriterInterceptor", - "javax.ws.rs.ext.ParamConverterProvider", - "javax.ws.rs.container.DynamicFeature", - "javax.ws.rs.client.ClientRequestFilter", - "javax.ws.rs.client.ClientResponseFilter", - "javax.ws.rs.client.RxInvokerProvider", - "javax.inject.Provider", - "javax.ws.rs.core.Feature", - "javax.ws.rs.ext.Providers", - "org.glassfish.jersey.internal.inject.Providers", - "org.glassfish.jersey.process.Inflector", - "org.glassfish.jersey.process.internal.Inflecting", - "org.glassfish.jersey.process.internal.RequestScope", - "org.glassfish.json.JsonProviderImpl" - ], - "classes": [ - ] -} \ No newline at end of file diff --git a/jersey/client/pom.xml b/jersey/client/pom.xml index dad43f87500..a582aa1123f 100644 --- a/jersey/client/pom.xml +++ b/jersey/client/pom.xml @@ -1,6 +1,6 @@ jakarta.xml.bind jakarta.xml.bind-api - - - jakarta.activation - jakarta.activation-api - - + + + jakarta.inject + jakarta.inject-api diff --git a/jersey/client/src/main/java/module-info.java b/jersey/client/src/main/java/module-info.java index 0e9c97f6db8..0ec29a0365c 100644 --- a/jersey/client/src/main/java/module-info.java +++ b/jersey/client/src/main/java/module-info.java @@ -21,6 +21,4 @@ requires transitive java.ws.rs; requires transitive jersey.common; requires transitive jersey.client; - - requires java.xml.bind; } diff --git a/jersey/server/pom.xml b/jersey/server/pom.xml index 784fe77df21..f3ae669ec1b 100644 --- a/jersey/server/pom.xml +++ b/jersey/server/pom.xml @@ -1,6 +1,6 @@ jakarta.inject jakarta.inject-api + + + jakarta.xml.bind + jakarta.xml.bind-api + diff --git a/jersey/server/src/main/java/module-info.java b/jersey/server/src/main/java/module-info.java index 51140b5df2e..a8a403caab0 100644 --- a/jersey/server/src/main/java/module-info.java +++ b/jersey/server/src/main/java/module-info.java @@ -25,6 +25,4 @@ requires transitive jakarta.inject.api; requires transitive jakarta.activation; requires transitive java.annotation; - - requires transitive java.xml.bind; } \ No newline at end of file diff --git a/media/common/src/main/java/io/helidon/media/common/ByteChannelBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/ByteChannelBodyWriter.java index d3bed5d4724..5cddd6a8a16 100644 --- a/media/common/src/main/java/io/helidon/media/common/ByteChannelBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/ByteChannelBodyWriter.java @@ -28,10 +28,12 @@ /** * Message body writer for {@link ReadableByteChannel}. */ -public final class ByteChannelBodyWriter implements MessageBodyWriter { +final class ByteChannelBodyWriter implements MessageBodyWriter { static final RetrySchema DEFAULT_RETRY_SCHEMA = RetrySchema.linear(0, 10, 250); + private static final ByteChannelBodyWriter DEFAULT = new ByteChannelBodyWriter(DEFAULT_RETRY_SCHEMA); + private final ByteChannelToChunks mapper; /** @@ -44,8 +46,8 @@ private ByteChannelBodyWriter(RetrySchema schema) { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { - return ReadableByteChannel.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(ReadableByteChannel.class, type); } @Override @@ -62,20 +64,21 @@ public Publisher write(Single content, * @param schema retry schema * @return {@link ReadableByteChannel} message body writer */ - public static ByteChannelBodyWriter create(RetrySchema schema) { + static ByteChannelBodyWriter create(RetrySchema schema) { return new ByteChannelBodyWriter(schema); } /** - * Create a new instance of {@link ByteChannelBodyWriter}. + * Return an instance of {@link ByteChannelBodyWriter}. + * * @return {@link ReadableByteChannel} message body writer */ - public static ByteChannelBodyWriter create() { - return new ByteChannelBodyWriter(DEFAULT_RETRY_SCHEMA); + static ByteChannelBodyWriter create() { + return DEFAULT; } /** - * Implementation of {@link MultiMapper} that converts a + * Implementation of {@link Mapper} that converts a * {@link ReadableByteChannel} to a publisher of {@link DataChunk}. */ private static final class ByteChannelToChunks implements Mapper> { diff --git a/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyWriter.java index ad8df48d7c6..c37266a0b8c 100644 --- a/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyWriter.java @@ -27,17 +27,19 @@ /** * Writer for {@code CharSequence}. */ -public final class CharSequenceBodyWriter implements MessageBodyWriter { +final class CharSequenceBodyWriter implements MessageBodyWriter { + + private static final CharSequenceBodyWriter DEFAULT = new CharSequenceBodyWriter(); /** - * Enforce the use of {@link #get()}. + * Enforce the use of {@link #create()}. */ private CharSequenceBodyWriter() { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { - return CharSequence.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(CharSequence.class, type); } @Override @@ -50,11 +52,12 @@ public Publisher write(Single content, } /** - * Create a new instance of {@link CharSequenceBodyWriter}. + * Return an instance of {@link CharSequenceBodyWriter}. + * * @return {@link CharSequence} message body writer. */ - public static CharSequenceBodyWriter create() { - return new CharSequenceBodyWriter(); + static CharSequenceBodyWriter create() { + return DEFAULT; } private static final class CharSequenceToChunks implements Mapper> { diff --git a/media/common/src/main/java/io/helidon/media/common/ContentReaders.java b/media/common/src/main/java/io/helidon/media/common/ContentReaders.java index da805389817..ebee456c53e 100644 --- a/media/common/src/main/java/io/helidon/media/common/ContentReaders.java +++ b/media/common/src/main/java/io/helidon/media/common/ContentReaders.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Flow.Publisher; @@ -53,7 +54,7 @@ private ContentReaders() { * @return Single */ public static Single readBytes(Publisher chunks) { - return Multi.from(chunks).collect(new BytesCollector()); + return Multi.create(chunks).collect(new BytesCollector()); } /** @@ -120,7 +121,9 @@ public static Reader byteArrayReader() { * {@link InputStream#read()}) block. * * @return a input stream content reader + * @deprecated use {@link DefaultMediaSupport#inputStreamReader()} */ + @Deprecated(since = "2.0.0") public static Reader inputStreamReader() { return (publisher, clazz) -> CompletableFuture.completedFuture(new DataChunkInputStream(publisher)); } @@ -182,7 +185,9 @@ private static final class BytesCollector implements Collector originalPublisher; + private int bufferIndex; private CompletableFuture current = new CompletableFuture<>(); private CompletableFuture next = current; private volatile Flow.Subscription subscription; @@ -97,8 +99,9 @@ private static void releaseChunk(DataChunk chunk, Throwable th) { @Override public void close() { // Assert: if current != next, next cannot ever be resolved with a chunk that needs releasing - current.whenComplete(DataChunkInputStream::releaseChunk); + Optional.ofNullable(current).ifPresent(it -> current.whenComplete(DataChunkInputStream::releaseChunk)); current = null; + bufferIndex = 0; } @Override @@ -132,32 +135,43 @@ public int read(byte[] buf, int off, int len) throws IOException { return -1; } - ByteBuffer currentBuffer = chunk.data(); - - if (currentBuffer.position() == 0) { - LOGGER.finest(() -> "Reading chunk ID: " + chunk.id()); - } - - // If there is anything to read, then read as much as fits into buf - int rem = currentBuffer.remaining(); - if (len > rem) { - len = rem; + ByteBuffer[] currentBuffers = chunk.data(); + int count = 0; + while (bufferIndex < currentBuffers.length) { + if (bufferIndex == 0 && currentBuffers[bufferIndex].position() == 0) { + LOGGER.finest(() -> "Reading chunk ID: " + chunk.id()); + } + + int rem = currentBuffers[bufferIndex].remaining(); + int blen = len; + if (blen > rem) { + blen = rem; + } + currentBuffers[bufferIndex].get(buf, off, blen); + off += blen; + count += blen; + len -= blen; + + if (rem > blen) { + break; + } + + // Chunk is consumed entirely - release the chunk, and prefetch a new chunk; do not + // wait for it to arrive - the next read may have to wait less. + // + // Assert: it is safe to request new chunks eagerly - there is no mechanism + // to push back unconsumed data, so we can assume we own all the chunks, + // consumed and unconsumed. + if (bufferIndex == currentBuffers.length - 1) { + releaseChunk(chunk, null); + current = next; + bufferIndex = 0; + subscription.request(1); + break; + } + bufferIndex++; } - currentBuffer.get(buf, off, len); - - // Chunk is consumed entirely - release the chunk, and prefetch a new chunk; do not - // wait for it to arrive - the next read may have to wait less. - // - // Assert: it is safe to request new chunks eagerly - there is no mechanism - // to push back unconsumed data, so we can assume we own all the chunks, - // consumed and unconsumed. - if (len == rem) { - releaseChunk(chunk, null); - current = next; - subscription.request(1); - } - - return len; + return count; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException(e); @@ -193,7 +207,7 @@ public void onSubscribe(Flow.Subscription subscription) { @Override public void onNext(DataChunk item) { LOGGER.finest(() -> "Processing chunk: " + item.id()); - if (item.data().remaining() > 0) { + if (item.remaining() > 0) { CompletableFuture prev = next; next = new CompletableFuture<>(); prev.complete(item); diff --git a/media/common/src/main/java/io/helidon/media/common/DefaultMediaSupport.java b/media/common/src/main/java/io/helidon/media/common/DefaultMediaSupport.java index 73045a4a3dd..982343154d2 100644 --- a/media/common/src/main/java/io/helidon/media/common/DefaultMediaSupport.java +++ b/media/common/src/main/java/io/helidon/media/common/DefaultMediaSupport.java @@ -15,42 +15,195 @@ */ package io.helidon.media.common; +import java.io.File; +import java.io.InputStream; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.Path; import java.util.Collection; import java.util.List; +import java.util.Objects; + +import io.helidon.common.reactive.RetrySchema; +import io.helidon.config.Config; /** * MediaSupport which registers default readers and writers to the contexts. */ public class DefaultMediaSupport implements MediaSupport { - private final boolean includeStackTraces; + private final ByteChannelBodyWriter byteChannelBodyWriter; + private final ThrowableBodyWriter throwableBodyWriter; - private DefaultMediaSupport(boolean includeStackTraces) { - this.includeStackTraces = includeStackTraces; + private DefaultMediaSupport(Builder builder) { + byteChannelBodyWriter = ByteChannelBodyWriter.create(builder.schema); + throwableBodyWriter = ThrowableBodyWriter.create(builder.includeStackTraces); } /** * Creates new instance of {@link DefaultMediaSupport}. * - * @param includeStackTraces include stack traces * @return new service instance */ - public static DefaultMediaSupport create(boolean includeStackTraces) { - return new DefaultMediaSupport(includeStackTraces); + public static DefaultMediaSupport create() { + return builder().build(); + } + + /** + * Return new {@link Builder} of the {@link DefaultMediaSupport}. + * + * @return default media support builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Return {@link String} reader instance. + * + * @return {@link String} reader + */ + public static MessageBodyReader stringReader() { + return StringBodyReader.create(); + } + + /** + * Return {@link InputStream} reader instance. + * + * @return {@link InputStream} reader + */ + public static MessageBodyReader inputStreamReader() { + return InputStreamBodyReader.create(); + } + + /** + * Return {@link CharSequence} writer instance. + * + * @return {@link CharSequence} writer + */ + public static MessageBodyWriter charSequenceWriter() { + return CharSequenceBodyWriter.create(); + } + + /** + * Create a new instance of {@link ReadableByteChannel} writer. + * + * @return {@link ReadableByteChannel} writer + */ + public static MessageBodyWriter byteChannelWriter() { + return ByteChannelBodyWriter.create(); + } + + /** + * Return new {@link ReadableByteChannel} writer instance with specific {@link RetrySchema}. + * + * @param schema retry schema + * @return {@link ReadableByteChannel} writer + */ + public static MessageBodyWriter byteChannelWriter(RetrySchema schema) { + return ByteChannelBodyWriter.create(schema); + } + + /** + * Return {@link Path} writer instance. + * + * @return {@link Path} writer + */ + public static MessageBodyWriter pathWriter() { + return PathBodyWriter.create(); + } + + /** + * Return {@link File} writer instance. + * + * @return {@link File} writer + */ + public static MessageBodyWriter fileWriter() { + return FileBodyWriter.create(); + } + + /** + * Return {@link Throwable} writer instance. + * + * @param includeStackTraces whether stack traces are to be written + * @return {@link Throwable} writer + */ + public static MessageBodyWriter throwableWriter(boolean includeStackTraces) { + return ThrowableBodyWriter.create(includeStackTraces); } @Override public Collection> readers() { - return List.of(StringBodyReader.create(), - InputStreamBodyReader.create()); + return List.of(stringReader(), + inputStreamReader()); } @Override public Collection> writers() { - return List.of(CharSequenceBodyWriter.create(), - ByteChannelBodyWriter.create(), - PathBodyWriter.create(), - FileBodyWriter.create(), - ThrowableBodyWriter.create(includeStackTraces)); + return List.of(charSequenceWriter(), + byteChannelBodyWriter, + pathWriter(), + fileWriter(), + throwableBodyWriter); + } + + /** + * Default media support builder. + */ + public static class Builder implements io.helidon.common.Builder { + + private boolean includeStackTraces = false; + private RetrySchema schema = ByteChannelBodyWriter.DEFAULT_RETRY_SCHEMA; + + private Builder() { + } + + @Override + public DefaultMediaSupport build() { + return new DefaultMediaSupport(this); + } + + /** + * Whether stack traces should be included in response. + * + * @param includeStackTraces include stack trace + * @return updated builder instance + */ + public Builder includeStackTraces(boolean includeStackTraces) { + this.includeStackTraces = includeStackTraces; + return this; + } + + /** + * Set specific {@link RetrySchema} to the byte channel. + * + * @param schema retry schema + * @return updated builder instance + */ + public Builder byteChannelRetrySchema(RetrySchema schema) { + this.schema = Objects.requireNonNull(schema); + return this; + } + + /** + * Configures this {@link DefaultMediaSupport.Builder} from the supplied {@link Config}. + * + * + * + * + * + * + * + * + * + * + *
Optional configuration parameters
keydescription
include-stack-tracesWhether stack traces should be included in response
+ * + * @param config media support config + * @return updated builder instance + */ + public Builder config(Config config) { + config.get("include-stack-traces").asBoolean().ifPresent(this::includeStackTraces); + return this; + } } } diff --git a/media/common/src/main/java/io/helidon/media/common/FileBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/FileBodyWriter.java index 502bbe80e23..7f14ac32f5a 100644 --- a/media/common/src/main/java/io/helidon/media/common/FileBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/FileBodyWriter.java @@ -35,17 +35,19 @@ /** * Message body writer for {@link File}. */ -public final class FileBodyWriter implements MessageBodyWriter { +final class FileBodyWriter implements MessageBodyWriter { + + private static final FileBodyWriter DEFAULT = new FileBodyWriter(); /** - * Enforces the use of {@link #get()}. + * Enforces the use of {@link #create()}. */ private FileBodyWriter() { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { - return File.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(File.class, type); } @Override @@ -56,15 +58,16 @@ public Publisher write(Single content, } /** - * Create a new {@link FileBodyWriter} instance. + * Return an instance of {@link FileBodyWriter}. + * * @return {@link File} message body writer. */ - public static FileBodyWriter create() { - return new FileBodyWriter(); + static FileBodyWriter create() { + return DEFAULT; } /** - * Implementation of {@link MultiMapper} that converts {@link File} to a + * Implementation of {@link Mapper} that converts {@link File} to a * publisher of {@link DataChunk}. */ private static final class FileToChunks implements Mapper> { diff --git a/media/common/src/main/java/io/helidon/media/common/InputStreamBodyReader.java b/media/common/src/main/java/io/helidon/media/common/InputStreamBodyReader.java index 353b55d09c4..bc938fa72d0 100644 --- a/media/common/src/main/java/io/helidon/media/common/InputStreamBodyReader.java +++ b/media/common/src/main/java/io/helidon/media/common/InputStreamBodyReader.java @@ -25,7 +25,9 @@ /** * Message body reader for {@link InputStream}. */ -public class InputStreamBodyReader implements MessageBodyReader { +class InputStreamBodyReader implements MessageBodyReader { + + private static final InputStreamBodyReader DEFAULT = new InputStreamBodyReader(); /** * Enforce the use of {@link #create()}. @@ -34,8 +36,12 @@ private InputStreamBodyReader() { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { - return InputStream.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + if (InputStream.class.equals(type.rawType()) + || DataChunkInputStream.class.equals(type.rawType())) { + return PredicateResult.SUPPORTED; + } + return PredicateResult.NOT_SUPPORTED; } @Override @@ -47,10 +53,11 @@ public Single read(Publisher publisher, Ge } /** - * Create a new instance of {@link InputStreamBodyReader}. + * Return an instance of {@link InputStreamBodyReader}. + * * @return {@link InputStream} message body reader. */ - public static InputStreamBodyReader create() { - return new InputStreamBodyReader(); + static InputStreamBodyReader create() { + return DEFAULT; } } diff --git a/media/common/src/main/java/io/helidon/media/common/MediaContext.java b/media/common/src/main/java/io/helidon/media/common/MediaContext.java index 0090822752f..8b93b15de72 100644 --- a/media/common/src/main/java/io/helidon/media/common/MediaContext.java +++ b/media/common/src/main/java/io/helidon/media/common/MediaContext.java @@ -15,9 +15,19 @@ */ package io.helidon.media.common; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.ServiceLoader; +import java.util.stream.Collectors; +import io.helidon.common.serviceloader.HelidonServiceLoader; import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.media.common.spi.MediaSupportProvider; /** * Media support. @@ -93,10 +103,27 @@ public MessageBodyWriterContext writerContext() { public static class Builder implements io.helidon.common.Builder, MediaContextBuilder { + private static final String SERVICE_NAME = "name"; + private static final String DEFAULTS_NAME = "defaults"; + + private static final int DEFAULTS_PRIORITY = 100; + private static final int BUILDER_PRIORITY = 200; + private static final int LOADER_PRIORITY = 300; + + private final HelidonServiceLoader.Builder services = HelidonServiceLoader + .builder(ServiceLoader.load(MediaSupportProvider.class)); + + private final List> builderReaders = new ArrayList<>(); + private final List> builderStreamReaders = new ArrayList<>(); + private final List> builderWriters = new ArrayList<>(); + private final List> builderStreamWriter = new ArrayList<>(); + private final List mediaSupports = new ArrayList<>(); + private final Map> servicesConfig = new HashMap<>(); private final MessageBodyReaderContext readerContext; private final MessageBodyWriterContext writerContext; private boolean registerDefaults = true; - private boolean includeStackTraces = false; + private boolean discoverServices = false; + private boolean filterServices = false; private Builder() { this.readerContext = MessageBodyReaderContext.create(); @@ -112,105 +139,189 @@ private Builder() { * description * * - * server-errors-include-stack-traces - * Whether stack traces should be included in the response (server only) - * - * * register-defaults * Whether to register default reader and writers * + * + * discover-services + * Whether to discover services via service loader + * + * + * filter-services + * Whether to filter discovered services by service names in services section + * + * + * services + * Configuration section for each service. Each entry has to have "name" parameter. + * It is also used for filtering of loaded services. + * * + * * @param config a {@link Config} * @return this {@link Builder} */ public Builder config(Config config) { - config.get("server-errors-include-stack-traces").asBoolean().ifPresent(this::includeStackTraces); config.get("register-defaults").asBoolean().ifPresent(this::registerDefaults); + config.get("discover-services").asBoolean().ifPresent(this::discoverServices); + config.get("filter-services").asBoolean().ifPresent(this::filterServices); + config.get("services") + .asNodeList() + .ifPresent(it -> it.forEach(serviceConfig -> { + String name = serviceConfig.get(SERVICE_NAME).asString().get(); + servicesConfig.merge(name, + serviceConfig.detach().asMap().orElseGet(Map::of), + (first, second) -> { + HashMap result = new HashMap<>(first); + result.putAll(second); + return result; + }); + })); return this; } @Override public Builder addMediaSupport(MediaSupport mediaSupport) { Objects.requireNonNull(mediaSupport); - mediaSupport.register(readerContext, writerContext); + mediaSupports.add(mediaSupport); + return this; + } + + /** + * Adds new instance of {@link MediaSupport} with specific priority. + * + * @param mediaSupport media support + * @param priority priority + * @return updated instance of the builder + */ + public Builder addMediaSupport(MediaSupport mediaSupport, int priority) { + Objects.requireNonNull(mediaSupport); + services.addService((config) -> mediaSupport, priority); return this; } @Override public Builder addReader(MessageBodyReader reader) { - readerContext.registerReader(reader); + builderReaders.add(reader); return this; } @Override public Builder addStreamReader(MessageBodyStreamReader streamReader) { - readerContext.registerReader(streamReader); + builderStreamReaders.add(streamReader); return this; } @Override public Builder addWriter(MessageBodyWriter writer) { - writerContext.registerWriter(writer); + builderWriters.add(writer); return this; } @Override public Builder addStreamWriter(MessageBodyStreamWriter streamWriter) { - writerContext.registerWriter(streamWriter); + builderStreamWriter.add(streamWriter); return this; } /** - * Register a new stream reader. - * @param reader reader to register - * @return this builder instance - */ - public Builder registerStreamReader(MessageBodyStreamReader reader) { - readerContext.registerReader(reader); - return this; - } - - /** - * Register a new stream writer. - * @param writer writer to register + * Whether defaults should be included. + * + * @param registerDefaults register defaults * @return this builder instance */ - public Builder registerStreamWriter(MessageBodyStreamWriter writer) { - writerContext.registerWriter(writer); + public Builder registerDefaults(boolean registerDefaults) { + this.registerDefaults = registerDefaults; return this; } /** - * Whether defaults should be included. + * Whether Java Service Loader should be used to load {@link MediaSupportProvider}. * - * @param registerDefaults register defaults + * @param discoverServices use Java Service Loader * @return this builder instance */ - public Builder registerDefaults(boolean registerDefaults) { - this.registerDefaults = registerDefaults; + public Builder discoverServices(boolean discoverServices) { + this.discoverServices = discoverServices; return this; } /** - * Whether stack traces should be included in response. - * - * This is server side setting. + * Whether services loaded by Java Service Loader should be filtered. + * All of the services which should pass the filter, have to be present under {@code services} section of configuration. * - * @param includeStackTraces include stack traces + * @param filterServices filter services * @return this builder instance */ - public Builder includeStackTraces(boolean includeStackTraces) { - this.includeStackTraces = includeStackTraces; + public Builder filterServices(boolean filterServices) { + this.filterServices = filterServices; return this; } @Override public MediaContext build() { + //Remove all service names from the obtained service configurations + servicesConfig.forEach((key, values) -> values.remove(SERVICE_NAME)); + if (filterServices) { + this.services.useSystemServiceLoader(false); + filterServices(); + } else { + this.services.useSystemServiceLoader(discoverServices); + } if (registerDefaults) { - addMediaSupport(DefaultMediaSupport.create(includeStackTraces)); + this.services.addService(new DefaultsProvider(), DEFAULTS_PRIORITY); } + this.services.defaultPriority(LOADER_PRIORITY) + .addService(config -> new MediaSupport() { + @Override + public void register(MessageBodyReaderContext readerContext, MessageBodyWriterContext writerContext) { + builderReaders.forEach(readerContext::registerReader); + builderStreamReaders.forEach(readerContext::registerReader); + builderWriters.forEach(writerContext::registerWriter); + builderStreamWriter.forEach(writerContext::registerWriter); + } + }, BUILDER_PRIORITY) + .addService(config -> new MediaSupport() { + @Override + public void register(MessageBodyReaderContext readerContext, MessageBodyWriterContext writerContext) { + mediaSupports.forEach(it -> it.register(readerContext, writerContext)); + } + }, BUILDER_PRIORITY) + .build() + .asList() + .stream() + .map(it -> it.create(Config.just(ConfigSources.create(servicesConfig.getOrDefault(it.configKey(), + new HashMap<>()))))) + .collect(Collectors.toCollection(LinkedList::new)) + .descendingIterator() + .forEachRemaining(mediaService -> mediaService.register(readerContext, writerContext)); + return new MediaContext(readerContext, writerContext); } + + private void filterServices() { + HelidonServiceLoader.builder(ServiceLoader.load(MediaSupportProvider.class)) + .defaultPriority(LOADER_PRIORITY) + .build() + .asList() + .stream() + .filter(provider -> servicesConfig.containsKey(provider.configKey())) + .forEach(services::addService); + } + } + + private static final class DefaultsProvider implements MediaSupportProvider { + + @Override + public String configKey() { + return Builder.DEFAULTS_NAME; + } + + @Override + public MediaSupport create(Config config) { + return DefaultMediaSupport.builder() + .config(config) + .build(); + } } } diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyContext.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyContext.java index b890bf6bbc7..cc034655b22 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyContext.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyContext.java @@ -21,7 +21,6 @@ import java.util.concurrent.Flow.Publisher; import java.util.concurrent.Flow.Subscriber; import java.util.concurrent.Flow.Subscription; -import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; @@ -224,20 +223,9 @@ public MessageBodyContext registerFilter(MessageBodyFilter filter) { return this; } - /** - * Register a function filter. - * - * @param function filter function - * @deprecated use {@link #registerFilter(MessageBodyFilter)} instead - */ - @Deprecated - public void registerFilter(Function, Publisher> function) { - Objects.requireNonNull(function, "filter function is null!"); - filters.registerLast(new FilterOperator(new FunctionFilter(function))); - } - /** * Apply the filters on the given input publisher to form a publisher chain. + * * @param publisher input publisher * @return tail of the publisher chain */ @@ -247,8 +235,9 @@ public Publisher applyFilters(Publisher publisher) { /** * Apply the filters on the given input publisher to form a publisher chain. + * * @param publisher input publisher - * @param type type information associated with the input publisher + * @param type type information associated with the input publisher * @return tail of the publisher chain */ protected Publisher applyFilters(Publisher publisher, GenericType type) { @@ -256,14 +245,15 @@ protected Publisher applyFilters(Publisher publisher, Gene if (eventListener != null) { return doApplyFilters(publisher, new TypedEventListener(eventListener, type)); } else { - return doApplyFilters(publisher, eventListener); + return doApplyFilters(publisher, null); } } /** * Perform the filter chaining. + * * @param publisher input publisher - * @param listener subscription listener + * @param listener subscription listener * @return tail of the publisher chain */ private Publisher doApplyFilters(Publisher publisher, EventListener listener) { @@ -273,8 +263,10 @@ private Publisher doApplyFilters(Publisher publisher, Even try { Publisher last = publisher; for (MessageBodyFilter filter : filters) { - last.subscribe(filter); - last = filter; + Publisher p = filter.apply(last); + if (p != null) { + last = p; + } } return new EventingPublisher(last, listener); } finally { @@ -367,89 +359,7 @@ public void onComplete() { } /** - * A filter adapter to support the old deprecated filter as function. - */ - private static final class FunctionFilter implements MessageBodyFilter { - - private final Function, Publisher> function; - private Subscriber subscriber; - private Subscription subscription; - private Throwable error; - private boolean completed; - private Publisher downstream; - - FunctionFilter(Function, Publisher> function) { - this.function = function; - } - - @Override - public void onSubscribe(Subscription s) { - this.subscription = s; - downstream = function.apply(new Publisher() { - @Override - public void subscribe(Subscriber subscriber) { - if (FunctionFilter.this.subscriber != null) { - subscriber.onError(new IllegalStateException("Already subscribed to!")); - } else { - FunctionFilter.this.subscriber = subscriber; - if (error != null) { - subscriber.onError(error); - } else if (completed) { - subscriber.onComplete(); - } else { - subscriber.onSubscribe(subscription); - } - } - } - }); - } - - @Override - public void onNext(DataChunk item) { - this.subscriber.onNext(item); - } - - @Override - public void onError(Throwable throwable) { - if (subscriber != null) { - subscriber.onError(throwable); - } else { - error = throwable; - } - } - - @Override - public void onComplete() { - if (this.subscriber != null) { - this.subscriber.onComplete(); - } else { - completed = true; - } - } - - @Override - public void subscribe(Subscriber s) { - if (downstream != null) { - downstream.subscribe(s); - } else { - if (subscriber == null) { - subscriber = s; - } - if (error != null) { - subscriber.onError(error); - } else if (completed) { - subscriber.onComplete(); - } else if (subscription != null) { - subscriber.onSubscribe(subscription); - } else { - subscriber.onError(new IllegalStateException("Not ready!")); - } - } - } - } - - /** - * {@link Operator} adapter for {@link Filter}. + * {@link MessageBodyOperator} adapter for {@link MessageBodyFilter}. */ private static final class FilterOperator implements MessageBodyOperator, MessageBodyFilter { @@ -460,33 +370,13 @@ private static final class FilterOperator implements MessageBodyOperator type, MessageBodyContext context) { - return this.getClass().equals(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyContext context) { + return PredicateResult.SUPPORTED; } @Override - public void onSubscribe(Subscription subscription) { - filter.onSubscribe(subscription); - } - - @Override - public void onNext(DataChunk item) { - filter.onNext(item); - } - - @Override - public void onError(Throwable throwable) { - filter.onError(throwable); - } - - @Override - public void onComplete() { - filter.onComplete(); - } - - @Override - public void subscribe(Subscriber subscriber) { - filter.subscribe(subscriber); + public Publisher apply(Publisher publisher) { + return filter.apply(publisher); } } diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyFilter.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyFilter.java index 8fa56af601e..6a780db2bad 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyFilter.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyFilter.java @@ -15,12 +15,14 @@ */ package io.helidon.media.common; -import java.util.concurrent.Flow.Processor; +import java.util.concurrent.Flow; +import java.util.function.Function; import io.helidon.common.http.DataChunk; /** - * Reactive contract for processing message body content. + * Function to filter or replace message body content publisher. + * It can be used for various purposes, for example data coding, logging, filtering, caching, etc. */ -public interface MessageBodyFilter extends Processor { +public interface MessageBodyFilter extends Function, Flow.Publisher> { } diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyFilters.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyFilters.java index a22960d163a..abedb148009 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyFilters.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyFilters.java @@ -15,16 +15,25 @@ */ package io.helidon.media.common; +import java.util.concurrent.Flow; + /** * Registry of {@link MessageBodyFilters}. */ public interface MessageBodyFilters { /** - * Register a message body filter. + * Registers a message body filter. + *

+ * The registered filters are applied to form a chain from the first registered to the last registered. + * The first evaluation of the function transforms the original publisher to a new publisher. Any subsequent + * evaluation receives the publisher transformed by the last previously registered filter. * - * @param filter message body filter to register - * @return MessageBodyFilters + * @param filter a function to map previously registered or original {@code Publisher} to the new one. If returns + * {@code null} then the result will be ignored. + * @return this instance of {@link MessageBodyFilters} + * @see MessageBodyContext#applyFilters(Flow.Publisher) + * @throws NullPointerException if parameter {@code function} is {@code null} */ MessageBodyFilters registerFilter(MessageBodyFilter filter); } diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyOperator.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyOperator.java index a6ace322b97..0b5bd662b0b 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyOperator.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyOperator.java @@ -30,8 +30,42 @@ public interface MessageBodyOperator { * * @param type the requested type * @param context the context providing the headers abstraction - * @return {@code true} if the operator can convert the specified type in - * the given context, {@code false} otherwise + * @return {@link PredicateResult} result */ - boolean accept(GenericType type, T context); + PredicateResult accept(GenericType type, T context); + + /** + * Status whether requested class type is supported by the operator. + */ + enum PredicateResult { + + /** + * Requested type not supported. + */ + NOT_SUPPORTED, + + /** + * Requested type is compatible with this operator, but it is not exact match. + */ + COMPATIBLE, + + /** + * Requested type is supported by that specific operator. + */ + SUPPORTED; + + /** + * Whether handled class is supported. + * Method {@link Class#isAssignableFrom(Class)} is invoked to verify if class under expected parameter is + * supported by by the class under actual parameter. + * + * @param expected expected type + * @param actual actual type + * @return if supported or not + */ + public static PredicateResult supports(Class expected, GenericType actual) { + return expected.isAssignableFrom(actual.rawType()) ? SUPPORTED : NOT_SUPPORTED; + } + + } } diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyOperators.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyOperators.java index 94ba5564350..67db80755da 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyOperators.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyOperators.java @@ -101,44 +101,26 @@ private void register(T operator, boolean addFirst) { , V extends MessageBodyContext> T select(GenericType type, V context) { Objects.requireNonNull(type, "type is null!"); Objects.requireNonNull(context, "context is null!"); - try { - lock.readLock().lock(); - for (T operator : operators) { - if (((U) operator).accept(type, context)) { - return operator; - } - } - } finally { - lock.readLock().unlock(); - } - if (parent != null) { - return parent.select(type, context); - } - return null; - } - - /** - * Select an operator by it class. - * @param operatorClass required operator class - * found in this registry hierarchy - * @return operator, or {@code null} or no operator was found - */ - T get(Class operatorClass) { - Objects.requireNonNull(operatorClass, "operatorClass is null!"); - try { - lock.readLock().lock(); - for (T operator : operators) { - if (operator.getClass().equals(operatorClass)) { - return operator; + T assignableOperator = null; + MessageBodyOperators current = this; + + while (current != null) { + try { + current.lock.readLock().lock(); + for (T operator : current.operators) { + MessageBodyOperator.PredicateResult accept = ((U) operator).accept(type, context); + if (accept == MessageBodyOperator.PredicateResult.COMPATIBLE && assignableOperator == null) { + assignableOperator = operator; + } else if (accept == MessageBodyOperator.PredicateResult.SUPPORTED) { + return operator; + } } + } finally { + current.lock.readLock().unlock(); } - } finally { - lock.readLock().unlock(); - } - if (parent != null) { - return parent.get(operatorClass); + current = current.parent; } - return null; + return assignableOperator; } @Override diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyReadableContent.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyReadableContent.java index a117b0ba181..2a2e798c876 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyReadableContent.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyReadableContent.java @@ -16,7 +16,6 @@ package io.helidon.media.common; import java.util.Objects; -import java.util.concurrent.CompletionStage; import java.util.concurrent.Flow.Publisher; import java.util.concurrent.Flow.Subscriber; import java.util.function.Function; @@ -25,6 +24,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; import io.helidon.common.reactive.Multi; +import io.helidon.common.reactive.Single; /** * Readable {@link MessageBodyContent}. @@ -88,7 +88,7 @@ public MessageBodyReadableContent registerReader(MessageBodyStreamReader read @Deprecated @Override public void registerFilter(Function, Publisher> function) { - context.registerFilter(function); + context.registerFilter(p -> function.apply(p)); } @Deprecated @@ -113,8 +113,8 @@ public void subscribe(Subscriber subscriber) { } @Override - public CompletionStage as(final Class type) { - return context.unmarshall(publisher, GenericType.create(type)).toStage(); + public Single as(final Class type) { + return context.unmarshall(publisher, GenericType.create(type)); } /** @@ -125,8 +125,8 @@ public CompletionStage as(final Class type) { * @param the requested type * @return a completion stage of the requested type */ - public CompletionStage as(final GenericType type) { - return context.unmarshall(publisher, type).toStage(); + public Single as(final GenericType type) { + return context.unmarshall(publisher, type); } /** @@ -150,7 +150,7 @@ public Multi asStream(Class type) { * @return a stream of entities */ public Multi asStream(GenericType type) { - return Multi.from(context.unmarshallStream(publisher, type)); + return Multi.create(context.unmarshallStream(publisher, type)); } /** diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyReaderContext.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyReaderContext.java index 6f65cda7d16..9695deb6a48 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyReaderContext.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyReaderContext.java @@ -21,18 +21,14 @@ import java.nio.charset.UnsupportedCharsetException; import java.util.Objects; import java.util.Optional; -import java.util.concurrent.CompletionStage; import java.util.concurrent.Flow; import java.util.concurrent.Flow.Publisher; -import java.util.concurrent.Flow.Subscriber; -import java.util.concurrent.Flow.Subscription; import java.util.function.Predicate; import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; import io.helidon.common.http.MediaType; import io.helidon.common.http.ReadOnlyParameters; -import io.helidon.common.reactive.CompletionSingle; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; @@ -121,7 +117,7 @@ public MessageBodyReaderContext registerReader(MessageBodyStreamReader reader * @param supported type * @param type class representing the supported type * @param reader reader function - * @deprecated use {@link #registerReader(MessageBodyReader) } instead + * @deprecated since 2.0.0 use {@link #registerReader(MessageBodyReader) } instead */ @Deprecated public void registerReader(Class type, io.helidon.common.http.Reader reader) { @@ -133,7 +129,7 @@ public void registerReader(Class type, io.helidon.common.http.Reader r * @param supported type * @param predicate class predicate * @param reader reader function - * @deprecated use {@link #registerReader(MessageBodyReader) } instead + * @deprecated since 2.0.0 use {@link #registerReader(MessageBodyReader) } instead */ @Deprecated public void registerReader(Predicate> predicate, io.helidon.common.http.Reader reader) { @@ -152,7 +148,7 @@ public void registerReader(Predicate> predicate, io.helidon.common. @SuppressWarnings("unchecked") public Single unmarshall(Publisher payload, GenericType type) { if (payload == null) { - return Single.empty(); + return Single.empty(); } // Flow.Publisher - can only be supported by streaming media @@ -182,16 +178,14 @@ public Single unmarshall(Publisher payload, GenericType typ * * @param entity type * @param payload inbound payload - * @param readerType the requested reader class + * @param reader specific reader * @param type actual representation of the entity type * @return publisher, never {@code null} */ - @SuppressWarnings("unchecked") - public Single unmarshall(Publisher payload, Class> readerType, - GenericType type) { - + public Single unmarshall(Publisher payload, MessageBodyReader reader, GenericType type) { + Objects.requireNonNull(reader); if (payload == null) { - return Single.empty(); + return Single.empty(); } // Flow.Publisher - can only be supported by streaming media @@ -202,10 +196,6 @@ public Single unmarshall(Publisher payload, Class filteredPayload = applyFilters(payload, type); - MessageBodyReader reader = (MessageBodyReader) readers.get(readerType); - if (reader == null) { - return readerNotFound(readerType.getTypeName()); - } return reader.read(filteredPayload, type, this); } catch (Throwable ex) { return transformationFailed(ex); @@ -224,7 +214,7 @@ public Single unmarshall(Publisher payload, Class Publisher unmarshallStream(Publisher payload, GenericType type) { if (payload == null) { - return Multi.empty(); + return Multi.empty(); } try { Publisher filteredPayload = applyFilters(payload, type); @@ -244,23 +234,19 @@ public Publisher unmarshallStream(Publisher payload, GenericTy * * @param entity type * @param payload inbound payload - * @param readerType the requested reader class + * @param reader specific reader * @param type actual representation of the entity type * @return publisher, never {@code null} */ @SuppressWarnings("unchecked") - public Publisher unmarshallStream(Publisher payload, Class> readerType, + public Publisher unmarshallStream(Publisher payload, MessageBodyStreamReader reader, GenericType type) { - + Objects.requireNonNull(reader); if (payload == null) { - return Multi.empty(); + return Multi.empty(); } try { Publisher filteredPayload = applyFilters(payload, type); - MessageBodyStreamReader reader = (MessageBodyStreamReader) sreaders.get(readerType); - if (reader == null) { - return readerNotFound(readerType.getTypeName()); - } return reader.read(filteredPayload, type, this); } catch (Throwable ex) { return transformationFailed(ex); @@ -396,62 +382,17 @@ private static final class ReaderAdapter implements MessageBodyReader { public Single read(Publisher publisher, GenericType type, MessageBodyReaderContext context) { - return new SingleFromCompletionStage(reader.applyAndCast(publisher, (Class) type.rawType())); + return Single.create(reader.applyAndCast(publisher, (Class) type.rawType())); } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { if (predicate != null) { - return predicate.test(type.rawType()); - } - return clazz.isAssignableFrom(type.rawType()); - } - } - - /** - * Single from future. - * @param item type - */ - private static final class SingleFromCompletionStage extends CompletionSingle { - - private final CompletionStage future; - private Subscriber subscriber; - private volatile boolean requested; - - SingleFromCompletionStage(CompletionStage future) { - this.future = Objects.requireNonNull(future, "future"); - } - - private void submit(T item) { - subscriber.onNext(item); - subscriber.onComplete(); - } - - private U raiseError(Throwable error) { - subscriber.onError(error); - return null; - } - - @Override - public void subscribe(Subscriber subscriber) { - if (this.subscriber != null) { - throw new IllegalStateException("Already subscribed to"); + return predicate.test(type.rawType()) + ? PredicateResult.SUPPORTED + : PredicateResult.NOT_SUPPORTED; } - this.subscriber = subscriber; - subscriber.onSubscribe(new Subscription() { - @Override - public void request(long n) { - if (n > 0 && !requested) { - future.exceptionally(SingleFromCompletionStage.this::raiseError); - future.thenAccept(SingleFromCompletionStage.this::submit); - requested = true; - } - } - - @Override - public void cancel() { - } - }); + return PredicateResult.supports(clazz, type); } } } diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java index cd2449cf6f3..43c578c2b20 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java @@ -27,6 +27,7 @@ import java.util.function.Predicate; import io.helidon.common.GenericType; +import io.helidon.common.LazyValue; import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; import io.helidon.common.http.MediaType; @@ -52,7 +53,7 @@ public final class MessageBodyWriterContext extends MessageBodyContext implement private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private final Parameters headers; - private final List acceptedTypes; + private final LazyValue> acceptedTypes; private final MessageBodyOperators> writers; private final MessageBodyOperators> swriters; private boolean contentTypeCached; @@ -64,7 +65,7 @@ public final class MessageBodyWriterContext extends MessageBodyContext implement * Private to enforce the use of the static factory methods. */ private MessageBodyWriterContext(MessageBodyWriterContext parent, EventListener eventListener, Parameters headers, - List acceptedTypes) { + LazyValue> acceptedTypes) { super(parent, eventListener); Objects.requireNonNull(headers, "headers cannot be null!"); @@ -72,7 +73,7 @@ private MessageBodyWriterContext(MessageBodyWriterContext parent, EventListener if (acceptedTypes != null) { this.acceptedTypes = acceptedTypes; } else { - this.acceptedTypes = List.of(); + this.acceptedTypes = LazyValue.create(List.of()); } if (parent != null) { this.writers = new MessageBodyOperators<>(parent.writers); @@ -93,7 +94,7 @@ private MessageBodyWriterContext(Parameters headers) { this.headers = headers; this.writers = new MessageBodyOperators<>(); this.swriters = new MessageBodyOperators<>(); - this.acceptedTypes = List.of(); + this.acceptedTypes = LazyValue.create(List.of()); } /** @@ -104,7 +105,7 @@ private MessageBodyWriterContext() { this.headers = ReadOnlyParameters.empty(); this.writers = new MessageBodyOperators<>(); this.swriters = new MessageBodyOperators<>(); - this.acceptedTypes = List.of(); + this.acceptedTypes = LazyValue.create(List.of()); this.contentTypeCache = Optional.empty(); this.contentTypeCached = true; this.charsetCache = DEFAULT_CHARSET; @@ -117,7 +118,7 @@ private MessageBodyWriterContext(MessageBodyWriterContext writerContext, Paramet this.headers = headers; this.writers = new MessageBodyOperators<>(writerContext.writers); this.swriters = new MessageBodyOperators<>(writerContext.swriters); - this.acceptedTypes = List.copyOf(writerContext.acceptedTypes); + this.acceptedTypes = writerContext.acceptedTypes; this.contentTypeCache = writerContext.contentTypeCache; this.contentTypeCached = writerContext.contentTypeCached; this.charsetCache = writerContext.charsetCache; @@ -136,7 +137,7 @@ private MessageBodyWriterContext(MessageBodyWriterContext writerContext, Paramet * @return MessageBodyWriterContext */ public static MessageBodyWriterContext create(MediaContext mediaContext, EventListener eventListener, Parameters headers, - List acceptedTypes) { + LazyValue> acceptedTypes) { if (mediaContext == null) { return new MessageBodyWriterContext(null, eventListener, headers, acceptedTypes); @@ -155,7 +156,7 @@ public static MessageBodyWriterContext create(MediaContext mediaContext, EventLi * @return MessageBodyWriterContext */ public static MessageBodyWriterContext create(MessageBodyWriterContext parent, EventListener eventListener, - Parameters headers, List acceptedTypes) { + Parameters headers, LazyValue> acceptedTypes) { return new MessageBodyWriterContext(parent, eventListener, headers, acceptedTypes); } @@ -218,7 +219,7 @@ public MessageBodyWriterContext registerWriter(MessageBodyStreamWriter writer * @param type class representing the type supported by this writer * @param function writer function * @return this {@code MessageBodyWriteableContent} instance - * @deprecated use {@link #registerWriter(MessageBodyWriter) } instead + * @deprecated since 2.0.0, use {@link #registerWriter(MessageBodyWriter) } instead */ @Deprecated public MessageBodyWriterContext registerWriter(Class type, Function> function) { @@ -234,7 +235,7 @@ public MessageBodyWriterContext registerWriter(Class type, Function MessageBodyWriterContext registerWriter(Class type, MediaType contentType, @@ -251,7 +252,7 @@ public MessageBodyWriterContext registerWriter(Class type, MediaType cont * @param accept the object predicate * @param function writer function * @return this {@code MessageBodyWriteableContent} instance - * @deprecated use {@link #registerWriter(MessageBodyWriter) } instead + * @deprecated since 2.0.0 use {@link #registerWriter(MessageBodyWriter) } instead */ @Deprecated public MessageBodyWriterContext registerWriter(Predicate accept, Function> function) { @@ -267,7 +268,7 @@ public MessageBodyWriterContext registerWriter(Predicate accept, Function * @param contentType the media type * @param function writer function * @return this {@code MessageBodyWriteableContent} instance - * @deprecated use {@link #registerWriter(MessageBodyWriter) } instead + * @deprecated since 2.0.0, use {@link #registerWriter(MessageBodyWriter) } instead */ @Deprecated public MessageBodyWriterContext registerWriter(Predicate accept, MediaType contentType, @@ -290,7 +291,7 @@ public MessageBodyWriterContext registerWriter(Predicate accept, MediaTyp public Publisher marshall(Single content, GenericType type) { try { if (content == null) { - return applyFilters(Multi.empty()); + return applyFilters(Multi.empty()); } if (byte[].class.equals(type.rawType())) { return applyFilters(((Single) content).flatMap(BYTES_MAPPER)); @@ -319,21 +320,15 @@ public Publisher marshall(Single content, GenericType type) * * @param entity type parameter * @param content input publisher - * @param writerType the requested writer class + * @param writer specific writer * @param type actual representation of the entity type * @return publisher, never {@code null} */ - @SuppressWarnings("unchecked") - public Publisher marshall(Single content, Class> writerType, - GenericType type) { - + public Publisher marshall(Single content, MessageBodyWriter writer, GenericType type) { + Objects.requireNonNull(writer); try { if (content == null) { - return applyFilters(Multi.empty()); - } - MessageBodyWriter writer = (MessageBodyWriter) writers.get(writerType); - if (writer == null) { - throw new IllegalStateException("No writer found for type: " + type); + return applyFilters(Multi.empty()); } return applyFilters(writer.write(content, type, this)); } catch (Throwable ex) { @@ -354,7 +349,7 @@ public Publisher marshall(Single content, Class Publisher marshallStream(Publisher content, GenericType type) { try { if (content == null) { - return applyFilters(Multi.empty()); + return applyFilters(Multi.empty()); } MessageBodyStreamWriter writer = (MessageBodyStreamWriter) swriters.select(type, this); if (writer == null) { @@ -372,21 +367,16 @@ public Publisher marshallStream(Publisher content, GenericType * * @param entity type parameter * @param content input publisher - * @param writerType the requested writer class + * @param writer specific writer * @param type actual representation of the entity type * @return publisher, never {@code null} */ - @SuppressWarnings("unchecked") - public Publisher marshallStream(Publisher content, Class> writerType, + public Publisher marshallStream(Publisher content, MessageBodyStreamWriter writer, GenericType type) { - + Objects.requireNonNull(writer); try { if (content == null) { - return applyFilters(Multi.empty()); - } - MessageBodyStreamWriter writer = (MessageBodyStreamWriter) swriters.get(writerType); - if (writer == null) { - throw new IllegalStateException("No stream writer found for type: " + type); + return applyFilters(Multi.empty()); } return applyFilters(writer.write(content, type, this)); } catch (Throwable ex) { @@ -426,7 +416,7 @@ public Optional contentType() { * @return List never {@code null} */ public List acceptedTypes() { - return acceptedTypes; + return acceptedTypes.get(); } /** @@ -478,10 +468,10 @@ public MediaType findAccepted(Predicate predicate, MediaType defaultT Objects.requireNonNull(defaultType, "defaultType cannot be null"); MediaType contentType = contentType().orElse(null); if (contentType == null) { - if (acceptedTypes.isEmpty()) { + if (acceptedTypes.get().isEmpty()) { return defaultType; } else { - for (final MediaType acceptedType : acceptedTypes) { + for (final MediaType acceptedType : acceptedTypes.get()) { if (predicate.test(acceptedType)) { if (acceptedType.isWildcardType() || acceptedType.isWildcardSubtype()) { return defaultType; @@ -507,7 +497,7 @@ public MediaType findAccepted(Predicate predicate, MediaType defaultT */ public MediaType findAccepted(MediaType mediaType) throws IllegalStateException { Objects.requireNonNull(mediaType, "mediaType cannot be null"); - for (MediaType acceptedType : acceptedTypes) { + for (MediaType acceptedType : acceptedTypes.get()) { if (mediaType.equals(acceptedType)) { return acceptedType; } @@ -569,22 +559,22 @@ private static final class WriterAdapter implements MessageBodyWriter { @Override @SuppressWarnings("unchecked") - public boolean accept(GenericType type, MessageBodyWriterContext context) { + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { if (this.type != null) { if (!this.type.isAssignableFrom(type.rawType())) { - return false; + return PredicateResult.NOT_SUPPORTED; } } else { if (!predicate.test((Object) type.rawType())) { - return false; + return PredicateResult.NOT_SUPPORTED; } } MediaType ct = context.contentType().orElse(null); if (!(contentType != null && ct != null && !ct.test(contentType))) { context.contentType(contentType); - return true; + return PredicateResult.SUPPORTED; } - return false; + return PredicateResult.NOT_SUPPORTED; } @Override diff --git a/media/common/src/main/java/io/helidon/media/common/PathBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/PathBodyWriter.java index 991f8e2a5c1..4a7b9a357f1 100644 --- a/media/common/src/main/java/io/helidon/media/common/PathBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/PathBodyWriter.java @@ -34,17 +34,19 @@ /** * Message body writer for {@link Path}. */ -public final class PathBodyWriter implements MessageBodyWriter { +final class PathBodyWriter implements MessageBodyWriter { + + private static final PathBodyWriter DEFAULT = new PathBodyWriter(); /** - * Enforces the use of {@link #get()}. + * Enforces the use of {@link #create()}. */ private PathBodyWriter() { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { - return Path.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(Path.class, type); } @Override @@ -58,12 +60,12 @@ public Publisher write(Single content, * Create a new of of {@link PathBodyWriter}. * @return new {@link Path} message body writer. */ - public static PathBodyWriter create() { - return new PathBodyWriter(); + static PathBodyWriter create() { + return DEFAULT; } /** - * Implementation of {@link MultiMapper} that converts a {@link Path} to a + * Implementation of {@link Mapper} that converts a {@link Path} to a * publisher of {@link DataChunk}. */ private static final class PathToChunks implements Mapper> { diff --git a/media/common/src/main/java/io/helidon/media/common/ReadableByteChannelPublisher.java b/media/common/src/main/java/io/helidon/media/common/ReadableByteChannelPublisher.java index c541e69df16..a673102421f 100644 --- a/media/common/src/main/java/io/helidon/media/common/ReadableByteChannelPublisher.java +++ b/media/common/src/main/java/io/helidon/media/common/ReadableByteChannelPublisher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -117,7 +117,7 @@ private boolean publishSingleOrFinish(Flow.Subscriber subscr) curentChunk = null; } - ByteBuffer bb = chunk.data(); + ByteBuffer bb = chunk.data()[0]; int count = 0; while (bb.remaining() > 0) { count = channel.read(bb); diff --git a/media/common/src/main/java/io/helidon/media/common/StringBodyReader.java b/media/common/src/main/java/io/helidon/media/common/StringBodyReader.java index d62dd8e32dc..733e2b2674c 100644 --- a/media/common/src/main/java/io/helidon/media/common/StringBodyReader.java +++ b/media/common/src/main/java/io/helidon/media/common/StringBodyReader.java @@ -24,17 +24,19 @@ /** * Message body reader for {@link String}. */ -public final class StringBodyReader implements MessageBodyReader { +final class StringBodyReader implements MessageBodyReader { + + private static final StringBodyReader DEFAULT = new StringBodyReader(); /** - * Private to enforce the use of {@link #get()}. + * Private to enforce the use of {@link #create()}. */ private StringBodyReader() { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { - return String.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + return PredicateResult.supports(String.class, type); } @Override @@ -49,7 +51,7 @@ public Single read(Publisher publisher, Generic * Create a new instance of {@link StringBodyReader}. * @return {@link String} message body reader. */ - public static StringBodyReader create() { - return new StringBodyReader(); + static StringBodyReader create() { + return DEFAULT; } } diff --git a/media/common/src/main/java/io/helidon/media/common/ThrowableBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/ThrowableBodyWriter.java index b6e380d6c75..7d2690c1f5f 100644 --- a/media/common/src/main/java/io/helidon/media/common/ThrowableBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/ThrowableBodyWriter.java @@ -16,6 +16,7 @@ package io.helidon.media.common; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.concurrent.Flow.Publisher; import io.helidon.common.GenericType; @@ -27,21 +28,24 @@ /** * Message body writer for {@link Throwable}. */ -public class ThrowableBodyWriter implements MessageBodyWriter { +class ThrowableBodyWriter implements MessageBodyWriter { - private final boolean writeStackTrace; + private static final ThrowableBodyWriter DEFAULT_TRUE = new ThrowableBodyWriter(true); + private static final ThrowableBodyWriter DEFAULT_FALSE = new ThrowableBodyWriter(false); + + private final boolean includeStackTraces; private ThrowableBodyWriter() { this(false); } - protected ThrowableBodyWriter(boolean writeStackTrace) { - this.writeStackTrace = writeStackTrace; + protected ThrowableBodyWriter(boolean includeStackTraces) { + this.includeStackTraces = includeStackTraces; } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { - return Throwable.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(Throwable.class, type); } @Override @@ -49,25 +53,21 @@ public Publisher write(Single content, GenericType type, MessageBodyWriterContext context) { context.contentType(MediaType.TEXT_PLAIN); - return content.flatMap(new ThrowableToChunks(context.charset())); - } - - /** - * Creates a new {@link ThrowableBodyWriter}. - * @return a new {@link ThrowableBodyWriter}; never {@code null} - * @see #create(boolean) - */ - public static ThrowableBodyWriter create() { - return create(false); + if (includeStackTraces) { + return content.flatMap(new ThrowableToChunks(context.charset())); + } else { + return ContentWriters.writeCharSequence("Unexpected exception occurred.", StandardCharsets.UTF_8); + } } /** - * Creates a new {@link ThrowableBodyWriter}. - * @param writeStackTrace whether stack traces are to be written + * Return an instance of {@link ThrowableBodyWriter}. + * + * @param includeStackTraces whether stack traces are to be written * @return a new {@link ThrowableBodyWriter}; never {@code null} */ - public static ThrowableBodyWriter create(boolean writeStackTrace) { - return new ThrowableBodyWriter(writeStackTrace); + static ThrowableBodyWriter create(boolean includeStackTraces) { + return includeStackTraces ? DEFAULT_TRUE : DEFAULT_FALSE; } private static final class ThrowableToChunks implements Mapper> { diff --git a/media/common/src/main/java/io/helidon/media/common/spi/MediaSupportProvider.java b/media/common/src/main/java/io/helidon/media/common/spi/MediaSupportProvider.java index 457939f2560..db398ba0d61 100644 --- a/media/common/src/main/java/io/helidon/media/common/spi/MediaSupportProvider.java +++ b/media/common/src/main/java/io/helidon/media/common/spi/MediaSupportProvider.java @@ -28,12 +28,14 @@ public interface MediaSupportProvider { * * @return name of the configuration node of this service */ - String type(); + default String configKey() { + return "unconfigured"; + } /** * Create a new service instance based on configuration. * - * @param config configuration of this service + * @param config configuration of this service, never null * @return a new media service instance */ MediaSupport create(Config config); diff --git a/media/common/src/test/java/io/helidon/media/common/DataChunkInputStreamTest.java b/media/common/src/test/java/io/helidon/media/common/DataChunkInputStreamTest.java index 89ae9adc3ae..de2489d196d 100644 --- a/media/common/src/test/java/io/helidon/media/common/DataChunkInputStreamTest.java +++ b/media/common/src/test/java/io/helidon/media/common/DataChunkInputStreamTest.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -31,6 +32,7 @@ import io.helidon.common.http.DataChunk; import io.helidon.common.reactive.BufferedEmittingPublisher; import io.helidon.common.reactive.Multi; +import io.helidon.common.reactive.Single; import org.junit.jupiter.api.Test; @@ -69,6 +71,26 @@ public void emptyChunk() throws IOException { assertThat(sb.toString(), is("foobar")); } + @Test + public void chunksWithManyBuffers() throws IOException { + InputStream is = new DataChunkInputStream(Multi.just( + DataChunk.create(ByteBuffer.wrap("foo".getBytes()), ByteBuffer.wrap(",xxx".getBytes()).position(4)), + DataChunk.create(ByteBuffer.wrap(",bar".getBytes()), ByteBuffer.wrap(",bob".getBytes())))); + int c; + StringBuilder sb = new StringBuilder(); + while ((c = is.read()) != -1) { + sb.append((char) c); + } + assertThat(sb.toString(), is("foo,bar,bob")); + } + + @Test + public void closeMoreTheOnce() throws IOException { + InputStream is = new DataChunkInputStream(Single.just(DataChunk.create("test".getBytes()))); + is.close(); + is.close(); + } + @Test public void differentThreads() throws Exception { List test_data = List.of("test0", "test1", "test2", "test3"); @@ -84,7 +106,7 @@ public void differentThreads() throws Exception { pub.complete(); }); Future receiveFuture = executorService.submit(() -> { - DataChunkInputStream chunkInputStream = new DataChunkInputStream(Multi.from(pub) + DataChunkInputStream chunkInputStream = new DataChunkInputStream(Multi.create(pub) .map(s -> DataChunk.create(s.getBytes()))); for (int i = 0; i < test_data.size(); i++) { try { diff --git a/media/jackson/common/pom.xml b/media/jackson/common/pom.xml deleted file mode 100644 index 39a0e3ad943..00000000000 --- a/media/jackson/common/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - helidon-media-jackson-project - io.helidon.media.jackson - 2.0.0-SNAPSHOT - - 4.0.0 - - helidon-media-jackson-common - Helidon Media Jackson Common - - - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.module - jackson-module-parameter-names - - - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - - - io.helidon.common - helidon-common-http - - - io.helidon.media - helidon-media-common - - - org.junit.jupiter - junit-jupiter-api - test - - - org.hamcrest - hamcrest-all - test - - - diff --git a/media/jackson/pom.xml b/media/jackson/pom.xml index 4bdf22a26d2..37c6ca02f62 100644 --- a/media/jackson/pom.xml +++ b/media/jackson/pom.xml @@ -26,13 +26,44 @@ 2.0.0-SNAPSHOT - pom - io.helidon.media.jackson - helidon-media-jackson-project + helidon-media-jackson Helidon Media Jackson - - common - server - + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.module + jackson-module-parameter-names + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + io.helidon.common + helidon-common-http + + + io.helidon.media + helidon-media-common + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + diff --git a/media/jackson/server/pom.xml b/media/jackson/server/pom.xml deleted file mode 100644 index 2a4c8620bd9..00000000000 --- a/media/jackson/server/pom.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - helidon-media-jackson-project - io.helidon.media.jackson - 2.0.0-SNAPSHOT - - 4.0.0 - - helidon-media-jackson-server - Helidon Media Jackson WebServer Support - - - Jackson Support for WebServer - - - - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.module - jackson-module-parameter-names - - - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - - - io.helidon.webserver - helidon-webserver - - - io.helidon.media.jackson - helidon-media-jackson-common - - - org.junit.jupiter - junit-jupiter-api - test - - - org.hamcrest - hamcrest-all - test - - - io.helidon.webserver - helidon-webserver-test-support - test - - - diff --git a/media/jackson/server/src/main/java/io/helidon/media/jackson/server/JacksonSupport.java b/media/jackson/server/src/main/java/io/helidon/media/jackson/server/JacksonSupport.java deleted file mode 100644 index d8e0adfb8ec..00000000000 --- a/media/jackson/server/src/main/java/io/helidon/media/jackson/server/JacksonSupport.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.media.jackson.server; - -import io.helidon.common.HelidonFeatures; -import io.helidon.common.HelidonFlavor; -import io.helidon.media.common.MediaSupport; -import io.helidon.media.jackson.common.JacksonBodyReader; -import io.helidon.media.jackson.common.JacksonBodyWriter; -import io.helidon.webserver.Handler; -import io.helidon.webserver.Routing; -import io.helidon.webserver.ServerRequest; -import io.helidon.webserver.ServerResponse; -import io.helidon.webserver.Service; -import io.helidon.webserver.WebServer; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; - - -/** - * A {@link Service} and a {@link Handler} that provides Jackson - * support to Helidon. - * - * @deprecated use {@link io.helidon.media.jackson.common.JacksonSupport} with {@link WebServer.Builder#addMediaSupport(MediaSupport)} - */ -@Deprecated -public final class JacksonSupport implements Service, Handler { - - static { - HelidonFeatures.register(HelidonFlavor.SE, "WebServer", "Jackson"); - } - - private final JacksonBodyReader reader; - private final JacksonBodyWriter writer; - - /** - * Creates a new {@link JacksonSupport}. - * - * @param objectMapper mapper, must not be {@code null} - * - * @exception NullPointerException if {@code objectMapper} is {@code null} - */ - private JacksonSupport(final ObjectMapper objectMapper) { - this.reader = JacksonBodyReader.create(objectMapper); - this.writer = JacksonBodyWriter.create(objectMapper); - } - - @Override - public void update(Routing.Rules rules) { - rules.any(this); - } - - @Override - public void accept(final ServerRequest request, final ServerResponse response) { - request.content().registerReader(reader); - response.registerWriter(writer); - request.next(); - } - - /** - * Creates a new {@link JacksonSupport}. - * - * @return a new {@link JacksonSupport} - */ - public static JacksonSupport create() { - final ObjectMapper mapper = new ObjectMapper() - .registerModule(new ParameterNamesModule()) - .registerModule(new Jdk8Module()) - .registerModule(new JavaTimeModule()); - return create(mapper); - } - - /** - * Creates a new {@link JacksonSupport}. - * - * @param objectMapper must not be {@code null} - * @return a new {@link JacksonSupport} - * - * @exception NullPointerException if {@code objectMapper} - * is {@code null} - */ - public static JacksonSupport create(ObjectMapper objectMapper) { - return new JacksonSupport(objectMapper); - } -} diff --git a/media/jackson/server/src/main/java/io/helidon/media/jackson/server/package-info.java b/media/jackson/server/src/main/java/io/helidon/media/jackson/server/package-info.java deleted file mode 100644 index ff0aa693d0b..00000000000 --- a/media/jackson/server/src/main/java/io/helidon/media/jackson/server/package-info.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This package contains Jackson support for {@link io.helidon.webserver.WebServer WebServer}'s - * {@link io.helidon.webserver.Routing Routing}. - *

- * For more information see {@link io.helidon.media.jackson.server.JacksonSupport JacksonSupport} documentation. - * - * @see io.helidon.media.jackson.server.JacksonSupport - * @see io.helidon.webserver.Routing - * @see com.fasterxml.jackson.databind.ObjectMapper - */ -package io.helidon.media.jackson.server; diff --git a/media/jackson/server/src/main/java/module-info.java b/media/jackson/server/src/main/java/module-info.java deleted file mode 100644 index 3207cad9700..00000000000 --- a/media/jackson/server/src/main/java/module-info.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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. - */ - -/** - * Jackson-based JSON serialization support for webserver. - */ -module io.helidon.media.jackson.server { - requires com.fasterxml.jackson.databind; - requires com.fasterxml.jackson.datatype.jdk8; - requires com.fasterxml.jackson.datatype.jsr310; - requires com.fasterxml.jackson.module.paramnames; - requires io.helidon.media.jackson.common; - requires io.helidon.webserver; - - exports io.helidon.media.jackson.server; -} diff --git a/media/jackson/server/src/test/java/io/helidon/media/jackson/server/JacksonSupportTest.java b/media/jackson/server/src/test/java/io/helidon/media/jackson/server/JacksonSupportTest.java deleted file mode 100644 index 57e06463607..00000000000 --- a/media/jackson/server/src/test/java/io/helidon/media/jackson/server/JacksonSupportTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.media.jackson.server; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -import io.helidon.common.GenericType; -import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; -import io.helidon.webserver.Handler; -import io.helidon.webserver.Routing; -import io.helidon.webserver.testsupport.MediaPublisher; -import io.helidon.webserver.testsupport.TestClient; -import io.helidon.webserver.testsupport.TestResponse; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * Tests {@link JsonSupport}. - */ -public class JacksonSupportTest { - - @Test - public void pingPong() throws Exception { - final Routing routing = Routing.builder() - .register(JacksonSupport.create()) - .post("/foo", Handler.create(Person.class, (req, res, person) -> { - res.send(person); - })) - .build(); - final String personJson = "{\"name\":\"Frank\"}"; - final TestResponse response = TestClient.create(routing) - .path("/foo") - .post(MediaPublisher.create(MediaType.APPLICATION_JSON.withCharset("UTF-8"), personJson)); - - assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), is(MediaType.APPLICATION_JSON.toString())); - final String json = response.asString().get(10, TimeUnit.SECONDS); - assertThat(json, is(personJson)); - } - - @Test - public void genericType() throws Exception { - GenericType> personsType = new GenericType>() {}; - final Routing routing = Routing.builder() - .register(JacksonSupport.create()) - .post("/foo", (req, res) -> { - req.content().as(personsType) - .thenAccept((List persons) -> { - res.send(persons); - }); - }) - .build(); - - final String personsJson = "[{\"name\":\"Frank\"},{\"name\":\"John\"}]"; - final TestResponse response = TestClient.create(routing) - .path("/foo") - .post(MediaPublisher.create(MediaType.APPLICATION_JSON.withCharset("UTF-8"), - personsJson)); - assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), - is(MediaType.APPLICATION_JSON.toString())); - final String json = response.asString().get(10, TimeUnit.SECONDS); - assertThat(json, is(personsJson)); - } - - public static final class Person { - - private String name; - - public Person() { - super(); - } - - public String getName() { - return this.name; - } - - public void setName(final String name) { - this.name = name; - } - - } -} diff --git a/media/jsonb/common/src/main/resources/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider b/media/jackson/src/main/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider similarity index 93% rename from media/jsonb/common/src/main/resources/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider rename to media/jackson/src/main/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider index ddac308d7e1..2288a30662d 100644 --- a/media/jsonb/common/src/main/resources/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider +++ b/media/jackson/src/main/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider @@ -14,4 +14,4 @@ # limitations under the License. # -io.helidon.media.jsonb.common.JsonbProvider +io.helidon.media.jackson.JacksonProvider diff --git a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyReader.java b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyReader.java similarity index 90% rename from media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyReader.java rename to media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyReader.java index 2cbd3a7f386..c71d098ab0b 100644 --- a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyReader.java +++ b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jackson.common; +package io.helidon.media.jackson; import java.io.IOException; import java.lang.reflect.ParameterizedType; @@ -36,7 +36,7 @@ /** * Message body reader supporting object binding with Jackson. */ -public final class JacksonBodyReader implements MessageBodyReader { +final class JacksonBodyReader implements MessageBodyReader { private final ObjectMapper objectMapper; @@ -46,10 +46,12 @@ private JacksonBodyReader(ObjectMapper objectMapper) { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { Class clazz = type.rawType(); return !CharSequence.class.isAssignableFrom(clazz) - && objectMapper.canDeserialize(objectMapper.constructType(clazz)); + && objectMapper.canDeserialize(objectMapper.constructType(clazz)) + ? PredicateResult.COMPATIBLE + : PredicateResult.NOT_SUPPORTED; } @Override diff --git a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyWriter.java b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyWriter.java similarity index 88% rename from media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyWriter.java rename to media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyWriter.java index aa8d04ccc59..6296771cd5a 100644 --- a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyWriter.java +++ b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jackson.common; +package io.helidon.media.jackson; import java.io.IOException; import java.nio.charset.Charset; @@ -35,7 +35,7 @@ /** * Message body writer supporting object binding with Jackson. */ -public final class JacksonBodyWriter implements MessageBodyWriter { +final class JacksonBodyWriter implements MessageBodyWriter { private final ObjectMapper objectMapper; @@ -45,9 +45,11 @@ private JacksonBodyWriter(ObjectMapper objectMapper) { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { return !CharSequence.class.isAssignableFrom(type.rawType()) - && objectMapper.canSerialize(type.rawType()); + && objectMapper.canSerialize(type.rawType()) + ? PredicateResult.COMPATIBLE + : PredicateResult.NOT_SUPPORTED; } @Override diff --git a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonProvider.java b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonProvider.java new file mode 100644 index 00000000000..49decaa075b --- /dev/null +++ b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonProvider.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.media.jackson; + +import java.util.stream.Stream; + +import io.helidon.config.Config; +import io.helidon.media.common.MediaSupport; +import io.helidon.media.common.spi.MediaSupportProvider; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; + +/** + * Jackson support SPI provider. + */ +public class JacksonProvider implements MediaSupportProvider { + + private static final String JACKSON = "jackson"; + + @Override + public MediaSupport create(Config config) { + ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new ParameterNamesModule()) + .registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()); + configureJackson(objectMapper, config); + return JacksonSupport.create(objectMapper); + } + + private void configureJackson(ObjectMapper objectMapper, Config config) { + Stream.of(DeserializationFeature.values()) + .forEach(df -> config.get(configName(df.name())).asBoolean() + .ifPresent(val -> objectMapper.configure(df, val))); + Stream.of(SerializationFeature.values()) + .forEach(sf -> config.get(configName(sf.name())).asBoolean() + .ifPresent(val -> objectMapper.configure(sf, val))); + Stream.of(JsonParser.Feature.values()) + .forEach(jp -> config.get(configName(jp.name())).asBoolean() + .ifPresent(val -> objectMapper.configure(jp, val))); + Stream.of(MapperFeature.values()) + .forEach(mf -> config.get(configName(mf.name())).asBoolean() + .ifPresent(val -> objectMapper.configure(mf, val))); + Stream.of(JsonGenerator.Feature.values()) + .forEach(jgf -> config.get(configName(jgf.name())).asBoolean() + .ifPresent(val -> objectMapper.configure(jgf, val))); + } + + private String configName(String enumName) { + return enumName.toLowerCase() + .replace('_', '-'); + } + + @Override + public String configKey() { + return JACKSON; + } +} diff --git a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonRuntimeException.java b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonRuntimeException.java similarity index 89% rename from media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonRuntimeException.java rename to media/jackson/src/main/java/io/helidon/media/jackson/JacksonRuntimeException.java index 0aa04e92333..87c2f04d5d4 100644 --- a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonRuntimeException.java +++ b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonRuntimeException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jackson.common; +package io.helidon.media.jackson; /** * A {@link RuntimeException} that indicates a problem was encountered diff --git a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonSupport.java b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonSupport.java similarity index 58% rename from media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonSupport.java rename to media/jackson/src/main/java/io/helidon/media/jackson/JacksonSupport.java index 029f630c859..9dec71c72dd 100644 --- a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonSupport.java +++ b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonSupport.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jackson.common; +package io.helidon.media.jackson; import java.util.Collection; import java.util.List; @@ -21,6 +21,7 @@ import io.helidon.common.HelidonFeatures; import io.helidon.common.HelidonFlavor; +import io.helidon.common.LazyValue; import io.helidon.media.common.MediaSupport; import io.helidon.media.common.MessageBodyReader; import io.helidon.media.common.MessageBodyWriter; @@ -33,7 +34,7 @@ /** * Support for Jackson integration. * - * For usage examples navigate to the {@link MediaSupport} + * For usage examples navigate to the {@link MediaSupport}. */ public final class JacksonSupport implements MediaSupport { @@ -45,82 +46,102 @@ public final class JacksonSupport implements MediaSupport { .registerModule(new ParameterNamesModule()) .registerModule(new Jdk8Module()) .registerModule(new JavaTimeModule()); + private static final LazyValue DEFAULT = LazyValue.create(() -> new JacksonSupport(MAPPER)); - private static final JacksonSupport DEFAULT_JACKSON = new JacksonSupport(MAPPER); - - private final ObjectMapper objectMapper; + private final JacksonBodyReader reader; + private final JacksonBodyWriter writer; private JacksonSupport(final ObjectMapper objectMapper) { - this.objectMapper = objectMapper; + this.reader = JacksonBodyReader.create(objectMapper); + this.writer = JacksonBodyWriter.create(objectMapper); } /** - * Creates new Jackson reader instance. + * Creates a new {@link JacksonSupport}. * - * @return Jackson reader instance + * @return a new {@link JacksonSupport} */ - public static JacksonBodyReader reader() { - return create().newReader(); + public static JacksonSupport create() { + return DEFAULT.get(); } /** - * Creates new Jackson writer instance. + * Creates a new {@link JacksonSupport}. * - * @return Jackson writer instance + * @param objectMapper must not be {@code null} + * @return a new {@link JacksonSupport} */ - public static JacksonBodyWriter writer() { - return create().newWriter(); + public static JacksonSupport create(ObjectMapper objectMapper) { + Objects.requireNonNull(objectMapper); + return new JacksonSupport(objectMapper); } /** - * Creates new Jackson reader instance. + * Return a default Jackson entity reader. * - * @return Jackson reader instance + * @return default Jackson body writer instance */ - public JacksonBodyReader newReader() { - return JacksonBodyReader.create(objectMapper); + public static MessageBodyReader reader() { + return DEFAULT.get().reader; } /** - * Creates new Jackson writer instance. + * Create a new Jackson entity reader based on {@link ObjectMapper} instance. * - * @return Jackson writer instance + * @param objectMapper object mapper instance + * @return new Jackson body reader instance */ - public JacksonBodyWriter newWriter() { - return JacksonBodyWriter.create(objectMapper); + public static MessageBodyReader reader(ObjectMapper objectMapper) { + Objects.requireNonNull(objectMapper); + return JacksonBodyReader.create(objectMapper); } - @Override - public Collection> readers() { - return List.of(newReader()); + /** + * Return a default Jackson entity writer. + * + * @return default Jackson body writer instance + */ + public static MessageBodyWriter writer() { + return DEFAULT.get().writer; } - @Override - public Collection> writers() { - return List.of(newWriter()); + /** + * Create a new Jackson entity writer based on {@link ObjectMapper} instance. + * + * @param objectMapper object mapper instance + * @return new Jackson body writer instance + */ + public static MessageBodyWriter writer(ObjectMapper objectMapper) { + Objects.requireNonNull(objectMapper); + return JacksonBodyWriter.create(objectMapper); } /** - * Creates a new {@link JacksonSupport}. + * Return Jackson reader instance. * - * @param objectMapper must not be {@code null} - * @return a new {@link JacksonSupport} - * - * @exception NullPointerException if {@code objectMapper} - * is {@code null} + * @return Jackson reader instance */ - public static JacksonSupport create(ObjectMapper objectMapper) { - Objects.requireNonNull(objectMapper); - return new JacksonSupport(objectMapper); + public MessageBodyReader readerInstance() { + return reader; } /** - * Creates a new {@link JacksonSupport}. + * Return Jackson writer instance. * - * @return a new {@link JacksonSupport} + * @return Jackson writer instance */ - public static JacksonSupport create() { - return DEFAULT_JACKSON; + public MessageBodyWriter writerInstance() { + return writer; + } + + @Override + public Collection> readers() { + return List.of(reader); + } + + @Override + public Collection> writers() { + return List.of(writer); } } diff --git a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/package-info.java b/media/jackson/src/main/java/io/helidon/media/jackson/package-info.java similarity index 84% rename from media/jackson/common/src/main/java/io/helidon/media/jackson/common/package-info.java rename to media/jackson/src/main/java/io/helidon/media/jackson/package-info.java index c743c4ab8e8..00aa4925447 100644 --- a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/package-info.java +++ b/media/jackson/src/main/java/io/helidon/media/jackson/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or 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,4 +16,4 @@ /** * Jackson media type support. */ -package io.helidon.media.jackson.common; +package io.helidon.media.jackson; diff --git a/media/jackson/common/src/main/java/module-info.java b/media/jackson/src/main/java/module-info.java similarity index 86% rename from media/jackson/common/src/main/java/module-info.java rename to media/jackson/src/main/java/module-info.java index e7feef7b91e..7732b577ab1 100644 --- a/media/jackson/common/src/main/java/module-info.java +++ b/media/jackson/src/main/java/module-info.java @@ -15,15 +15,15 @@ */ import io.helidon.media.common.spi.MediaSupportProvider; -import io.helidon.media.jackson.common.JacksonSupport; -import io.helidon.media.jackson.common.JacksonProvider; +import io.helidon.media.jackson.JacksonSupport; +import io.helidon.media.jackson.JacksonProvider; /** * Jackson support common classes. * * @see JacksonSupport */ -module io.helidon.media.jackson.common { +module io.helidon.media.jackson { requires com.fasterxml.jackson.databind; requires com.fasterxml.jackson.core; @@ -37,7 +37,7 @@ requires io.helidon.media.common; requires io.helidon.config; - exports io.helidon.media.jackson.common; + exports io.helidon.media.jackson; provides MediaSupportProvider with JacksonProvider; } diff --git a/media/jackson/common/src/test/java/io/helidon/media/jackson/common/JacksonBodyReaderTest.java b/media/jackson/src/test/java/io/helidon/media/jackson/JacksonBodyReaderTest.java similarity index 83% rename from media/jackson/common/src/test/java/io/helidon/media/jackson/common/JacksonBodyReaderTest.java rename to media/jackson/src/test/java/io/helidon/media/jackson/JacksonBodyReaderTest.java index 18d3bf84f10..ca3cf07a5c0 100644 --- a/media/jackson/common/src/test/java/io/helidon/media/jackson/common/JacksonBodyReaderTest.java +++ b/media/jackson/src/test/java/io/helidon/media/jackson/JacksonBodyReaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or 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,18 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jackson.common; +package io.helidon.media.jackson; + +import java.nio.charset.StandardCharsets; +import java.util.List; -import com.fasterxml.jackson.databind.ObjectMapper; import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; import io.helidon.common.reactive.Single; import io.helidon.media.common.MessageBodyReaderContext; -import org.junit.jupiter.api.Assertions; + +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import java.nio.charset.StandardCharsets; -import java.util.List; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; public class JacksonBodyReaderTest { @@ -36,8 +40,8 @@ void testDeserializeWithGenerics() throws Exception { }, MessageBodyReaderContext.create()) .get(); - Assertions.assertEquals(1, books.size()); - Assertions.assertTrue(books.get(0) instanceof Book); + assertThat(books.size(), is(1)); + assertThat(books.get(0), notNullValue()); } public static class Book { diff --git a/media/jsonb/common/pom.xml b/media/jsonb/common/pom.xml deleted file mode 100644 index 1e480b51f42..00000000000 --- a/media/jsonb/common/pom.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - helidon-media-jsonb-project - io.helidon.media.jsonb - 2.0.0-SNAPSHOT - - 4.0.0 - - helidon-media-jsonb-common - Helidon Media JSON-B Common - - - - io.helidon.common - helidon-common-http - - - io.helidon.media - helidon-media-common - - - org.eclipse - yasson - - - org.glassfish - jakarta.json - runtime - - - org.junit.jupiter - junit-jupiter-api - test - - - org.hamcrest - hamcrest-all - test - - - diff --git a/media/jsonb/pom.xml b/media/jsonb/pom.xml index 76fed60f9cd..47548d0c8d4 100644 --- a/media/jsonb/pom.xml +++ b/media/jsonb/pom.xml @@ -26,13 +26,47 @@ 2.0.0-SNAPSHOT - pom - io.helidon.media.jsonb - helidon-media-jsonb-project + helidon-media-jsonb Helidon Media JSON-B - - common - server - + + + io.helidon.common + helidon-common-http + + + io.helidon.media + helidon-media-common + + + org.eclipse + yasson + + + org.glassfish + jakarta.json + runtime + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + io.helidon.webserver + helidon-webserver + test + + + io.helidon.webserver + helidon-webserver-test-support + test + + + diff --git a/media/jsonb/server/pom.xml b/media/jsonb/server/pom.xml deleted file mode 100644 index 2df7a77dbfc..00000000000 --- a/media/jsonb/server/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - helidon-media-jsonb-project - io.helidon.media.jsonb - 2.0.0-SNAPSHOT - - 4.0.0 - - helidon-media-jsonb-server - Helidon Media JSON-B WebServer Support - - - JSON-B Support for WebServer - - - - - jakarta.json.bind - jakarta.json.bind-api - - - io.helidon.media.jsonb - helidon-media-jsonb-common - - - io.helidon.webserver - helidon-webserver - - - org.junit.jupiter - junit-jupiter-api - test - - - org.hamcrest - hamcrest-all - test - - - io.helidon.webserver - helidon-webserver-test-support - test - - - diff --git a/media/jsonb/server/src/main/java/io/helidon/media/jsonb/server/JsonBindingSupport.java b/media/jsonb/server/src/main/java/io/helidon/media/jsonb/server/JsonBindingSupport.java deleted file mode 100644 index 0016a783879..00000000000 --- a/media/jsonb/server/src/main/java/io/helidon/media/jsonb/server/JsonBindingSupport.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.media.jsonb.server; - -import java.util.Objects; - -import javax.json.bind.Jsonb; -import javax.json.bind.JsonbBuilder; - -import io.helidon.common.HelidonFeatures; -import io.helidon.common.HelidonFlavor; -import io.helidon.media.common.MediaSupport; -import io.helidon.media.jsonb.common.JsonbBodyReader; -import io.helidon.media.jsonb.common.JsonbBodyWriter; -import io.helidon.webserver.Handler; -import io.helidon.webserver.Routing; -import io.helidon.webserver.ServerRequest; -import io.helidon.webserver.ServerResponse; -import io.helidon.webserver.Service; -import io.helidon.webserver.WebServer; - -/** - * A {@link Service} and a {@link Handler} that provides JSON-B support to Helidon. - * - * @deprecated use {@link io.helidon.media.jsonb.common.JsonbSupport} with {@link WebServer.Builder#addMediaSupport(MediaSupport)} - */ -@Deprecated -public final class JsonBindingSupport implements Service, Handler { - - static { - HelidonFeatures.register(HelidonFlavor.SE, "WebServer", "JSON-B"); - } - - private final JsonbBodyReader reader; - private final JsonbBodyWriter writer; - - private JsonBindingSupport(final Jsonb jsonb) { - this.reader = JsonbBodyReader.create(jsonb); - this.writer = JsonbBodyWriter.create(jsonb); - } - - @Override - public void update(Routing.Rules rules) { - rules.any(this); - } - - @Override - public void accept(final ServerRequest request, final ServerResponse response) { - request.content().registerReader(reader); - response.registerWriter(writer); - request.next(); - } - - /** - * Creates a new {@link JsonBindingSupport}. - * - * @param jsonb the JSON-B to use; must not be {@code null} - * - * @return a new {@link JsonBindingSupport} - * - * @exception NullPointerException if {@code jsonb} is {@code - * null} - */ - public static JsonBindingSupport create(final Jsonb jsonb) { - Objects.requireNonNull(jsonb); - return new JsonBindingSupport(jsonb); - } - - /** - * Creates a new {@link JsonBindingSupport}. - * - * @return a new {@link JsonBindingSupport} - */ - public static JsonBindingSupport create() { - final Jsonb jsonb = JsonbBuilder.create(); - return create(jsonb); - } - -} diff --git a/media/jsonb/server/src/main/java/io/helidon/media/jsonb/server/package-info.java b/media/jsonb/server/src/main/java/io/helidon/media/jsonb/server/package-info.java deleted file mode 100644 index d495f52e963..00000000000 --- a/media/jsonb/server/src/main/java/io/helidon/media/jsonb/server/package-info.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This package contains JSON-B ({@code javax.json.bind}) support for {@link io.helidon.webserver.WebServer WebServer}'s - * {@link io.helidon.webserver.Routing Routing}. - *

- * For more information see {@link io.helidon.media.jsonb.server.JsonBindingSupport JsonSupport} documentation. - * - * @see io.helidon.media.jsonb.server.JsonBindingSupport - * @see io.helidon.webserver.Routing - * @see javax.json.bind.Jsonb - */ -package io.helidon.media.jsonb.server; diff --git a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyReader.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyReader.java similarity index 88% rename from media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyReader.java rename to media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyReader.java index 018abaaf85c..9ca6311db5a 100644 --- a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyReader.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jsonb.common; +package io.helidon.media.jsonb; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -35,7 +35,7 @@ /** * Message body writer supporting object binding with JSON-B. */ -public class JsonbBodyReader implements MessageBodyReader { +class JsonbBodyReader implements MessageBodyReader { private final Jsonb jsonb; @@ -45,8 +45,10 @@ private JsonbBodyReader(Jsonb jsonb) { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { - return !CharSequence.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + return !CharSequence.class.isAssignableFrom(type.rawType()) + ? PredicateResult.COMPATIBLE + : PredicateResult.NOT_SUPPORTED; } @Override diff --git a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyWriter.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyWriter.java similarity index 90% rename from media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyWriter.java rename to media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyWriter.java index 6b508b8ec40..7105b60be66 100644 --- a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyWriter.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyWriter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jsonb.common; +package io.helidon.media.jsonb; import java.nio.charset.Charset; import java.util.Objects; @@ -35,7 +35,7 @@ /** * Message body writer supporting object binding with JSON-B. */ -public class JsonbBodyWriter implements MessageBodyWriter { +class JsonbBodyWriter implements MessageBodyWriter { private final Jsonb jsonb; @@ -45,10 +45,12 @@ private JsonbBodyWriter(Jsonb jsonb) { } @Override - public boolean accept(GenericType type, - MessageBodyWriterContext context) { + public PredicateResult accept(GenericType type, + MessageBodyWriterContext context) { - return !CharSequence.class.isAssignableFrom(type.rawType()); + return !CharSequence.class.isAssignableFrom(type.rawType()) + ? PredicateResult.COMPATIBLE + : PredicateResult.NOT_SUPPORTED; } @Override diff --git a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbProvider.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbProvider.java similarity index 71% rename from media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbProvider.java rename to media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbProvider.java index 35f769974d8..532f110a1a7 100644 --- a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbProvider.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbProvider.java @@ -14,7 +14,11 @@ * limitations under the License. */ -package io.helidon.media.jsonb.common; +package io.helidon.media.jsonb; + +import javax.json.bind.Jsonb; +import javax.json.bind.JsonbBuilder; +import javax.json.bind.JsonbConfig; import io.helidon.config.Config; import io.helidon.media.common.MediaSupport; @@ -29,11 +33,14 @@ public class JsonbProvider implements MediaSupportProvider { @Override public MediaSupport create(Config config) { - return JsonbSupport.create(); + JsonbConfig jsonbConfig = new JsonbConfig(); + config.asMap().ifPresent(map -> map.forEach(jsonbConfig::setProperty)); + Jsonb jsonb = JsonbBuilder.create(jsonbConfig); + return JsonbSupport.create(jsonb); } @Override - public String type() { + public String configKey() { return JSON_B; } } diff --git a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbSupport.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbSupport.java similarity index 53% rename from media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbSupport.java rename to media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbSupport.java index dbedbdd60f0..71ce91dcea5 100644 --- a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbSupport.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbSupport.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jsonb.common; +package io.helidon.media.jsonb; import java.util.Collection; import java.util.List; @@ -24,6 +24,7 @@ import io.helidon.common.HelidonFeatures; import io.helidon.common.HelidonFlavor; +import io.helidon.common.LazyValue; import io.helidon.media.common.MediaSupport; import io.helidon.media.common.MessageBodyReader; import io.helidon.media.common.MessageBodyWriter; @@ -31,92 +32,118 @@ /** * Support for JSON-B integration. * - * For usage examples navigate to the {@link MediaSupport} + * For usage examples navigate to the {@link MediaSupport}. * * @see Jsonb */ public final class JsonbSupport implements MediaSupport { static { - HelidonFeatures.register(HelidonFlavor.SE, "WebServer", "JSON-B"); + HelidonFeatures.register(HelidonFlavor.SE, "Media", "JSON-B"); } private static final Jsonb JSON_B = JsonbBuilder.create(); - private static final JsonbSupport DEFAULT = new JsonbSupport(JSON_B); + private static final LazyValue DEFAULT = LazyValue.create(() -> new JsonbSupport(JSON_B)); - private final Jsonb jsonb; - private JsonbSupport(final Jsonb jsonb) { - this.jsonb = jsonb; + private final JsonbBodyReader reader; + private final JsonbBodyWriter writer; + + private JsonbSupport(Jsonb jsonb) { + this.reader = JsonbBodyReader.create(jsonb); + this.writer = JsonbBodyWriter.create(jsonb); } /** - * Creates new JSON-B reader instance. + * Creates a new {@link JsonbSupport}. * - * @return JSON-B reader instance + * @return a new {@link JsonbSupport} */ - public static JsonbBodyReader reader() { - return create().newReader(); + public static JsonbSupport create() { + return DEFAULT.get(); } /** - * Creates new JSON-B writer instance. + * Creates a new {@link JsonbSupport}. * - * @return JSON-B writer instance + * @param jsonb the JSON-B to use; must not be {@code null} + * + * @return a new {@link JsonbSupport} + * + * @exception NullPointerException if {@code jsonb} is {@code + * null} */ - public static JsonbBodyWriter writer() { - return create().newWriter(); + public static JsonbSupport create(Jsonb jsonb) { + Objects.requireNonNull(jsonb); + return new JsonbSupport(jsonb); } /** - * Creates new JSON-B reader instance. + * Return a default JSON-B entity reader. * - * @return JSON-B reader instance + * @return default JSON-B body writer instance */ - public JsonbBodyReader newReader() { - return JsonbBodyReader.create(jsonb); + public static MessageBodyReader reader() { + return DEFAULT.get().reader; } /** - * Creates new JSON-B writer instance. + * Create a new JSON-B entity reader based on {@link Jsonb} instance. * - * @return JSON-B writer instance + * @param jsonb jsonb instance + * @return new JSON-B body reader instance */ - public JsonbBodyWriter newWriter() { - return JsonbBodyWriter.create(jsonb); + public static MessageBodyReader reader(Jsonb jsonb) { + Objects.requireNonNull(jsonb); + return JsonbBodyReader.create(jsonb); } - @Override - public Collection> readers() { - return List.of(newReader()); + /** + * Return a default JSON-B entity writer. + * + * @return default JSON-B body writer instance + */ + public static MessageBodyWriter writer() { + return DEFAULT.get().writer; } - @Override - public Collection> writers() { - return List.of(newWriter()); + /** + * Create a new JSON-B entity writer based on {@link Jsonb} instance. + * + * @param jsonb jsonb instance + * @return new JSON-B body writer instance + */ + public static MessageBodyWriter writer(Jsonb jsonb) { + Objects.requireNonNull(jsonb); + return JsonbBodyWriter.create(jsonb); } /** - * Creates a new {@link JsonbSupport}. - * - * @param jsonb the JSON-B to use; must not be {@code null} - * - * @return a new {@link JsonbSupport} + * Return JSON-B reader instance. * - * @exception NullPointerException if {@code jsonb} is {@code - * null} + * @return JSON-B reader instance */ - public static JsonbSupport create(final Jsonb jsonb) { - Objects.requireNonNull(jsonb); - return new JsonbSupport(jsonb); + public MessageBodyReader readerInstance() { + return reader; } /** - * Creates a new {@link JsonbSupport}. + * Return JSON-B writer instance. * - * @return a new {@link JsonbSupport} + * @return JSON-B writer instance */ - public static JsonbSupport create() { - return DEFAULT; + public MessageBodyWriter writerInstance() { + return writer; + } + + @Override + public Collection> readers() { + return List.of(reader); } + + @Override + public Collection> writers() { + return List.of(writer); + } + } diff --git a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/package-info.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/package-info.java similarity index 84% rename from media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/package-info.java rename to media/jsonb/src/main/java/io/helidon/media/jsonb/package-info.java index 23a85cf27d8..39a1b2a2b84 100644 --- a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/package-info.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or 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,4 +16,4 @@ /** * JSON-B media type support. */ -package io.helidon.media.jsonb.common; +package io.helidon.media.jsonb; diff --git a/media/jsonb/common/src/main/java/module-info.java b/media/jsonb/src/main/java/module-info.java similarity index 79% rename from media/jsonb/common/src/main/java/module-info.java rename to media/jsonb/src/main/java/module-info.java index e33d035da2d..00647ad895a 100644 --- a/media/jsonb/common/src/main/java/module-info.java +++ b/media/jsonb/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or 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,15 +15,15 @@ */ import io.helidon.media.common.spi.MediaSupportProvider; -import io.helidon.media.jsonb.common.JsonbProvider; -import io.helidon.media.jsonb.common.JsonbSupport; +import io.helidon.media.jsonb.JsonbProvider; +import io.helidon.media.jsonb.JsonbSupport; /** * JSON-B support common classes. * * @see JsonbSupport */ -module io.helidon.media.jsonb.common { +module io.helidon.media.jsonb { requires io.helidon.common; requires io.helidon.common.http; @@ -33,7 +33,7 @@ requires io.helidon.media.common; requires transitive java.json.bind; - exports io.helidon.media.jsonb.common; + exports io.helidon.media.jsonb; provides MediaSupportProvider with JsonbProvider; } diff --git a/media/jsonb/common/src/main/resources/META-INF/native-image/native-image.properties b/media/jsonb/src/main/resources/META-INF/native-image/native-image.properties similarity index 90% rename from media/jsonb/common/src/main/resources/META-INF/native-image/native-image.properties rename to media/jsonb/src/main/resources/META-INF/native-image/native-image.properties index 42c32692856..4be00033753 100644 --- a/media/jsonb/common/src/main/resources/META-INF/native-image/native-image.properties +++ b/media/jsonb/src/main/resources/META-INF/native-image/native-image.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2019, 2020 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/media/jsonp/common/src/main/resources/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider b/media/jsonb/src/main/resources/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider similarity index 93% rename from media/jsonp/common/src/main/resources/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider rename to media/jsonb/src/main/resources/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider index 5657b042627..1bbe0a4f3f7 100644 --- a/media/jsonp/common/src/main/resources/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider +++ b/media/jsonb/src/main/resources/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider @@ -14,4 +14,4 @@ # limitations under the License. # -io.helidon.media.jsonp.common.JsonpProvider +io.helidon.media.jsonb.JsonbProvider diff --git a/media/jsonb/server/src/test/java/io/helidon/media/jsonb/server/TestJsonBindingSupport.java b/media/jsonb/src/test/java/io/helidon/media/jsonb/TestJsonBindingSupport.java similarity index 83% rename from media/jsonb/server/src/test/java/io/helidon/media/jsonb/server/TestJsonBindingSupport.java rename to media/jsonb/src/test/java/io/helidon/media/jsonb/TestJsonBindingSupport.java index 6ac644790a4..8a35fd2a658 100644 --- a/media/jsonb/server/src/test/java/io/helidon/media/jsonb/server/TestJsonBindingSupport.java +++ b/media/jsonb/src/test/java/io/helidon/media/jsonb/TestJsonBindingSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jsonb.server; +package io.helidon.media.jsonb; import java.util.List; import java.util.concurrent.TimeUnit; @@ -36,20 +36,17 @@ import static org.hamcrest.MatcherAssert.assertThat; /** - * Tests {@link JsonBindingSupport}. + * Tests {@link JsonbSupport}. */ public class TestJsonBindingSupport { - private static final Jsonb JSONB = JsonbBuilder.create(); - @Test public void pingPong() throws Exception { final Routing routing = Routing.builder() - .register(JsonBindingSupport.create(JSONB)) .post("/foo", Handler.create(Person.class, (req, res, person) -> res.send(person))) .build(); final String personJson = "{\"name\":\"Frank\"}"; - final TestResponse response = TestClient.create(routing) + final TestResponse response = TestClient.create(routing, JsonbSupport.create()) .path("/foo") .post(MediaPublisher.create(MediaType.APPLICATION_JSON.withCharset("UTF-8"), personJson)); @@ -61,19 +58,16 @@ public void pingPong() throws Exception { @Test public void genericType() throws Exception { - GenericType> personsType = new GenericType>() {}; + GenericType> personsType = new GenericType<>() { }; final Routing routing = Routing.builder() - .register(JsonBindingSupport.create(JSONB)) .post("/foo", (req, res) -> { req.content().as(personsType) - .thenAccept((List persons) -> { - res.send(persons); - }); + .thenAccept(res::send); }) .build(); final String personsJson = "[{\"name\":\"Frank\"},{\"name\":\"John\"}]"; - final TestResponse response = TestClient.create(routing) + final TestResponse response = TestClient.create(routing, JsonbSupport.create()) .path("/foo") .post(MediaPublisher.create(MediaType.APPLICATION_JSON.withCharset("UTF-8"), personsJson)); diff --git a/media/jsonp/common/pom.xml b/media/jsonp/common/pom.xml deleted file mode 100644 index 0e8cef6c9e7..00000000000 --- a/media/jsonp/common/pom.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - helidon-media-jsonp-project - io.helidon.media.jsonp - 2.0.0-SNAPSHOT - - 4.0.0 - - helidon-media-jsonp-common - Helidon Media JSON-P Common - - - etc/spotbugs/exclude.xml - - - - - io.helidon.common - helidon-common-http - - - io.helidon.media - helidon-media-common - - - org.glassfish - jakarta.json - - - org.junit.jupiter - junit-jupiter-api - test - - - org.hamcrest - hamcrest-all - test - - - diff --git a/media/jsonp/common/etc/spotbugs/exclude.xml b/media/jsonp/etc/spotbugs/exclude.xml similarity index 93% rename from media/jsonp/common/etc/spotbugs/exclude.xml rename to media/jsonp/etc/spotbugs/exclude.xml index 9688f1f815b..664e87838f6 100644 --- a/media/jsonp/common/etc/spotbugs/exclude.xml +++ b/media/jsonp/etc/spotbugs/exclude.xml @@ -27,7 +27,7 @@ try-with-resource blocks). --> - + diff --git a/media/jsonp/pom.xml b/media/jsonp/pom.xml index c5c1149016d..8037e3b60d2 100644 --- a/media/jsonp/pom.xml +++ b/media/jsonp/pom.xml @@ -26,13 +26,46 @@ 2.0.0-SNAPSHOT - pom - io.helidon.media.jsonp - helidon-media-jsonp-project + helidon-media-jsonp Helidon Media JSON-P - - common - server - + + etc/spotbugs/exclude.xml + + + + + io.helidon.common + helidon-common-http + + + io.helidon.media + helidon-media-common + + + org.glassfish + jakarta.json + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + io.helidon.webserver + helidon-webserver + test + + + io.helidon.webserver + helidon-webserver-test-support + test + + + diff --git a/media/jsonp/server/pom.xml b/media/jsonp/server/pom.xml deleted file mode 100644 index 91b1dd884ff..00000000000 --- a/media/jsonp/server/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - helidon-media-jsonp-project - io.helidon.media.jsonp - 2.0.0-SNAPSHOT - - 4.0.0 - - helidon-media-jsonp-server - Helidon Media JSON-P WebServer Support - - - JSON-P Support for WebServer - - - - - io.helidon.webserver - helidon-webserver - - - io.helidon.common - helidon-common-reactive - - - io.helidon.media.jsonp - helidon-media-jsonp-common - - - org.junit.jupiter - junit-jupiter-api - test - - - org.hamcrest - hamcrest-all - test - - - org.mockito - mockito-core - test - - - io.helidon.webserver - helidon-webserver-test-support - test - - - diff --git a/media/jsonp/server/src/main/java/io/helidon/media/jsonp/server/JsonSupport.java b/media/jsonp/server/src/main/java/io/helidon/media/jsonp/server/JsonSupport.java deleted file mode 100644 index 664170bede2..00000000000 --- a/media/jsonp/server/src/main/java/io/helidon/media/jsonp/server/JsonSupport.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.media.jsonp.server; - -import javax.json.JsonStructure; - -import io.helidon.common.HelidonFeatures; -import io.helidon.common.HelidonFlavor; -import io.helidon.media.common.MediaSupport; -import io.helidon.media.jsonp.common.JsonpBodyReader; -import io.helidon.media.jsonp.common.JsonpBodyWriter; -import io.helidon.media.jsonp.common.JsonpSupport; -import io.helidon.webserver.Handler; -import io.helidon.webserver.Routing; -import io.helidon.webserver.ServerRequest; -import io.helidon.webserver.ServerResponse; -import io.helidon.webserver.Service; -import io.helidon.webserver.WebServer; - - -/** - * It provides contains JSON-P ({@code javax.json}) support for - * {@link WebServer WebServer}'s {@link Routing}. It is intended to provide - * readers and writers for {@code javax.json} objects such as - * {@link javax.json.JsonObject JsonObject} or - * {@link javax.json.JsonArray JsonArray}. If registered on the - * {@code Web Server} {@link Routing}, then all {@link Handler Handlers} can use - * {@code ServerRequest.}{@link ServerRequest#content() content()}{@code .} - * {@link io.helidon.common.http.Content#as(Class) as(...)} and - * {@code ServerResponse.}{@link ServerResponse#send(Object) send()} with - * {@link JsonStructure JSON} objects. - * - *

Get Instance

- * Use factory methods {@link #create()} or - * {@link #create(JsonpSupport)} to acquire an - * instance. - * - *

Usage with Routing

{@code JsonSupport} should be registered on the - * routing before any business logic handlers. - *
{@code
- * Routing.builder()
- *        .register(JsonSupport.create())
- *        .etc.... // Business logic related handlers
- * }
Instance behaves also as a routing filter. It means that it can be - * registered on any routing rule (for example HTTP method) and then it can be - * used in following handlers with compatible rules. - *
{@code
- * // Register JsonSupport only for POST of 'foo'
- * Routing.builder()
- *        .post("/foo/{}", JsonSupport.create())
- *        .post("/foo/bar", ...) // It can use JSON structures
- *        .get("/foo/bar", ...);  // It can NOT use JSON structures
- * }
- * - * @deprecated use {@link io.helidon.media.jsonp.common.JsonpSupport} with {@link WebServer.Builder#addMediaSupport(MediaSupport)} - * @see Routing - * @see JsonStructure - * @see JsonpBodyReader - * @see JsonpBodyWriter - */ -@Deprecated -public final class JsonSupport implements Service, Handler { - - static { - HelidonFeatures.register(HelidonFlavor.SE, "WebServer", "JSON-P"); - } - - private static final JsonSupport INSTANCE = new JsonSupport(JsonpSupport.create()); - - private final JsonpBodyReader reader; - private final JsonpBodyWriter writer; - - private JsonSupport(JsonpSupport processing) { - reader = processing.newReader(); - writer = processing.newWriter(); - } - - @Override - public void update(Routing.Rules rules) { - rules.any(this); - } - - @Override - public void accept(final ServerRequest request, final ServerResponse response) { - request.content().registerReader(reader); - response.registerWriter(writer); - request.next(); - } - - JsonpBodyReader reader() { - return reader; - } - - /** - * Returns a singleton instance of JsonSupport with default configuration. - *

- * Use {@link #create(JsonpSupport)} method - * to create a new instance with specific configuration. - * - * @return a singleton instance with default configuration - */ - public static JsonSupport create() { - return INSTANCE; - } - - /** - * Create a JsonSupport with customized processing configuration. - * - * @param processing processing to get JSON-P readers and writers - * @return JsonSupport to register with web server - * @see JsonpSupport#builder() - */ - public static JsonSupport create(JsonpSupport processing) { - if (null == processing) { - throw new NullPointerException("JsonProcessing argument must not be null."); - } - return new JsonSupport(processing); - } -} diff --git a/media/jsonp/server/src/main/java/io/helidon/media/jsonp/server/package-info.java b/media/jsonp/server/src/main/java/io/helidon/media/jsonp/server/package-info.java deleted file mode 100644 index 22e3eaa7bff..00000000000 --- a/media/jsonp/server/src/main/java/io/helidon/media/jsonp/server/package-info.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This package contains JSON-P ({@code javax.json}) support for {@link io.helidon.webserver.WebServer WebServer}'s - * {@link io.helidon.webserver.Routing Routing}. - *

- * For more information see {@link io.helidon.media.jsonp.server.JsonSupport JsonSupport} documentation. - * - * @see io.helidon.media.jsonp.server.JsonSupport - * @see io.helidon.webserver.Routing - * @see javax.json.Json - * @see javax.json.JsonStructure - */ -package io.helidon.media.jsonp.server; diff --git a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyReader.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyReader.java similarity index 88% rename from media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyReader.java rename to media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyReader.java index 23050b376ac..545f023865e 100644 --- a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyReader.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jsonp.common; +package io.helidon.media.jsonp; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -37,7 +37,7 @@ /** * Message body reader for {@link JsonStructure} sub-classes (JSON-P). */ -public final class JsonpBodyReader implements MessageBodyReader { +final class JsonpBodyReader implements MessageBodyReader { private final JsonReaderFactory jsonFactory; @@ -47,8 +47,8 @@ public final class JsonpBodyReader implements MessageBodyReader { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { - return JsonStructure.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + return PredicateResult.supports(JsonStructure.class, type); } @Override diff --git a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyStreamWriter.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyStreamWriter.java similarity index 88% rename from media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyStreamWriter.java rename to media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyStreamWriter.java index cba215e48bc..22b3eeb854e 100644 --- a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyStreamWriter.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyStreamWriter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jsonp.common; +package io.helidon.media.jsonp; import java.nio.charset.StandardCharsets; import java.util.concurrent.Flow.Publisher; @@ -29,12 +29,12 @@ import io.helidon.common.reactive.Single; import io.helidon.media.common.MessageBodyStreamWriter; import io.helidon.media.common.MessageBodyWriterContext; -import io.helidon.media.jsonp.common.JsonpBodyWriter.JsonStructureToChunks; +import io.helidon.media.jsonp.JsonpBodyWriter.JsonStructureToChunks; /** * Message body writer for {@link javax.json.JsonStructure} sub-classes (JSON-P). */ -public class JsonpBodyStreamWriter implements MessageBodyStreamWriter { +class JsonpBodyStreamWriter implements MessageBodyStreamWriter { private static final byte[] ARRAY_JSON_END_BYTES = "]".getBytes(StandardCharsets.UTF_8); private static final byte[] ARRAY_JSON_BEGIN_BYTES = "[".getBytes(StandardCharsets.UTF_8); private static final byte[] COMMA_BYTES = ",".getBytes(StandardCharsets.UTF_8); @@ -46,8 +46,8 @@ public class JsonpBodyStreamWriter implements MessageBodyStreamWriter type, MessageBodyWriterContext context) { - return JsonStructure.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(JsonStructure.class, type); } @Override @@ -65,7 +65,7 @@ public Multi write(Publisher publisher, context.charset()); return Single.just(DataChunk.create(ARRAY_JSON_BEGIN_BYTES)) - .onCompleteResumeWith(Multi.from(publisher) + .onCompleteResumeWith(Multi.create(publisher) .map(jsonToChunks) .flatMap(it -> { if (first.getAndSet(false)) { diff --git a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyWriter.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyWriter.java similarity index 90% rename from media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyWriter.java rename to media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyWriter.java index 633cd73e22d..9ea0eba70ff 100644 --- a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyWriter.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyWriter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jsonp.common; +package io.helidon.media.jsonp; import java.nio.charset.Charset; import java.util.concurrent.Flow.Publisher; @@ -34,7 +34,7 @@ /** * Message body writer for {@link JsonStructure} sub-classes (JSON-P). */ -public class JsonpBodyWriter implements MessageBodyWriter { +class JsonpBodyWriter implements MessageBodyWriter { private final JsonWriterFactory jsonWriterFactory; @@ -43,8 +43,8 @@ public class JsonpBodyWriter implements MessageBodyWriter { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { - return JsonStructure.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(JsonStructure.class, type); } @Override diff --git a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpProvider.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpProvider.java similarity index 89% rename from media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpProvider.java rename to media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpProvider.java index b827c7b68da..8aeb5131f88 100644 --- a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpProvider.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.helidon.media.jsonp.common; +package io.helidon.media.jsonp; import io.helidon.config.Config; import io.helidon.media.common.MediaSupport; @@ -29,11 +29,11 @@ public class JsonpProvider implements MediaSupportProvider { @Override public MediaSupport create(Config config) { - return JsonpSupport.create(); + return JsonpSupport.create(config.asMap().get()); } @Override - public String type() { + public String configKey() { return JSON_P; } } diff --git a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpSupport.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpSupport.java similarity index 59% rename from media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpSupport.java rename to media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpSupport.java index 7f3c23a45e9..f6c815cd3be 100644 --- a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpSupport.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpSupport.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jsonp.common; +package io.helidon.media.jsonp; import java.util.Collection; import java.util.HashMap; @@ -22,8 +22,12 @@ import javax.json.Json; import javax.json.JsonReaderFactory; +import javax.json.JsonStructure; import javax.json.JsonWriterFactory; +import io.helidon.common.HelidonFeatures; +import io.helidon.common.HelidonFlavor; +import io.helidon.common.LazyValue; import io.helidon.media.common.MediaSupport; import io.helidon.media.common.MessageBodyReader; import io.helidon.media.common.MessageBodyStreamWriter; @@ -32,113 +36,163 @@ /** * Support for JSON Processing integration. * - * For usage examples navigate to {@link MediaSupport} + * For usage examples navigate to {@link MediaSupport}. */ public final class JsonpSupport implements MediaSupport { - private final JsonReaderFactory jsonReaderFactory; - private final JsonWriterFactory jsonWriterFactory; + static { + HelidonFeatures.register(HelidonFlavor.SE, "Media", "JSON-P"); + } + + private static final LazyValue DEFAULT = + LazyValue.create(() -> new JsonpSupport(Builder.readerFactory(null), + Builder.writerFactory(null))); + + private final JsonpBodyReader reader; + private final JsonpBodyWriter writer; + private final JsonpBodyStreamWriter streamWriter; private JsonpSupport(JsonReaderFactory readerFactory, JsonWriterFactory writerFactory) { - this.jsonReaderFactory = readerFactory; - this.jsonWriterFactory = writerFactory; + reader = new JsonpBodyReader(readerFactory); + writer = new JsonpBodyWriter(writerFactory); + streamWriter = new JsonpBodyStreamWriter(writerFactory); } /** - * Create a new JSON-P entity reader. + * Provides a default instance for JSON-P readers and writers. * - * @return JsonEntityReader + * @return json processing with default configuration */ - public JsonpBodyReader newReader() { - return new JsonpBodyReader(jsonReaderFactory); + public static JsonpSupport create() { + return DEFAULT.get(); } /** - * Create a new JSON-P entity writer. + * Create an instance with the provided JSON-P configuration. * - * @return JsonEntityWriter + * @param jsonPConfig configuration of the processing library + * @return a configured JSON-P instance */ - public JsonpBodyWriter newWriter() { - return new JsonpBodyWriter(jsonWriterFactory); + public static JsonpSupport create(Map jsonPConfig) { + return builder().jsonProcessingConfig(jsonPConfig).build(); } /** - * Create a new JSON-P stream writer. - *

- * This stream writer supports {@link java.util.concurrent.Flow.Publisher publishers} - * of {@link javax.json.JsonStructure} (such as {@link javax.json.JsonObject}) - * , writing them as an array of JSONs. + * Fluent API builder to create instances of JSON-P. * - * @return JSON processing stream writer. + * @return a new builder instance */ - public JsonpBodyStreamWriter newStreamWriter() { - return new JsonpBodyStreamWriter(jsonWriterFactory); + public static Builder builder() { + return new Builder(); } - @Override - public Collection> readers() { - return List.of(newReader()); + /** + * Return a default JSON-P entity reader. + * + * @return default JSON-P body reader instance + */ + public static MessageBodyReader reader() { + return DEFAULT.get().reader; } - @Override - public Collection> writers() { - return List.of(newWriter()); + /** + * Create a new JSON-P entity reader based on {@link JsonReaderFactory}. + * + * @param readerFactory json reader factory + * @return new JSON-P body reader instance + */ + public static MessageBodyReader reader(JsonReaderFactory readerFactory) { + return new JsonpBodyReader(readerFactory); } - @Override - public Collection> streamWriters() { - return List.of(newStreamWriter()); + /** + * Return a default JSON-P entity writer. + * + * @return default JSON-P body writer instance + */ + public static MessageBodyWriter writer() { + return DEFAULT.get().writer; } /** - * Provides a default instance for JSON-P readers and writers. - * @return json processing with default configuration + * Create a new JSON-P entity writer based on {@link JsonWriterFactory}. + * + * @param writerFactory json writer factory + * @return new JSON-P body writer instance */ - public static JsonpSupport create() { - return Builder.DEFAULT_INSTANCE; + public static MessageBodyWriter writer(JsonWriterFactory writerFactory) { + return new JsonpBodyWriter(writerFactory); } /** - * Create an instance with the provided JSON-P configuration. - * @param jsonPConfig configuration of the processing library - * @return a configured JSON-P instance + * Return a default JSON-P entity stream writer. + * + * @return default JSON-P body stream writer instance */ - public static JsonpSupport create(Map jsonPConfig) { - return builder().jsonProcessingConfig(jsonPConfig).build(); + public static MessageBodyStreamWriter streamWriter() { + return DEFAULT.get().streamWriter; } /** - * Create a new JSON-P entity reader. + * Create a new JSON-P entity stream writer based on {@link JsonWriterFactory}. * - * @return JsonEntityReader + * @param writerFactory json writer factory + * @return new JSON-P stream body writer instance */ - public static JsonpBodyReader reader() { - return create().newReader(); + public static MessageBodyStreamWriter streamWriter(JsonWriterFactory writerFactory) { + return new JsonpBodyStreamWriter(writerFactory); } /** - * Create a new JSON-P entity writer. + * Return JSON-P reader instance. * - * @return JsonEntityReader + * @return JSON-P reader instance */ - public static JsonpBodyWriter writer() { - return create().newWriter(); + public MessageBodyReader readerInstance() { + return reader; } /** - * Fluent API builder to create instances of JSON-P. + * Return JSON-P entity writer. * - * @return a new builder instance + * @return JSON-P writer instance */ - public static Builder builder() { - return new Builder(); + public MessageBodyWriter writerInstance() { + return writer; + } + + /** + * Return JSON-P stream writer. + *

+ * This stream writer supports {@link java.util.concurrent.Flow.Publisher publishers} + * of {@link javax.json.JsonStructure} (such as {@link javax.json.JsonObject}), + * writing them as an array of JSONs. + * + * @return JSON processing stream writer. + */ + public MessageBodyStreamWriter streamWriterInstance() { + return streamWriter; + } + + @Override + public Collection> readers() { + return List.of(reader); + } + + @Override + public Collection> writers() { + return List.of(writer); + } + + @Override + public Collection> streamWriters() { + return List.of(streamWriter); } /** * Fluent-API builder for {@link JsonpSupport}. */ public static class Builder implements io.helidon.common.Builder { - private static final JsonpSupport DEFAULT_INSTANCE = new JsonpSupport(readerFactory(null), writerFactory(null)); private JsonWriterFactory jsonWriterFactory; private JsonReaderFactory jsonReaderFactory; @@ -147,7 +201,7 @@ public static class Builder implements io.helidon.common.Builder { @Override public JsonpSupport build() { if ((null == jsonReaderFactory) && (null == jsonWriterFactory) && (null == jsonPConfig)) { - return DEFAULT_INSTANCE; + return DEFAULT.get(); } if (null == jsonPConfig) { @@ -188,6 +242,7 @@ public Builder jsonProcessingConfig(Map config) { /** * Explicit JSON-P Writer factory instance. + * * @param factory writer factory * @return updated builder instance */ @@ -198,6 +253,7 @@ public Builder jsonWriterFactory(JsonWriterFactory factory) { /** * Explicit JSON-P Reader factory instance. + * * @param factory reader factory * @return updated builder instance */ diff --git a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/package-info.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/package-info.java similarity index 84% rename from media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/package-info.java rename to media/jsonp/src/main/java/io/helidon/media/jsonp/package-info.java index 7af1e17920d..5a126a88a3b 100644 --- a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/package-info.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or 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,4 +16,4 @@ /** * JSON-P media type support. */ -package io.helidon.media.jsonp.common; +package io.helidon.media.jsonp; diff --git a/media/jsonp/common/src/main/java/module-info.java b/media/jsonp/src/main/java/module-info.java similarity index 85% rename from media/jsonp/common/src/main/java/module-info.java rename to media/jsonp/src/main/java/module-info.java index eac3f1636f0..01aa9a03d46 100644 --- a/media/jsonp/common/src/main/java/module-info.java +++ b/media/jsonp/src/main/java/module-info.java @@ -15,15 +15,15 @@ */ import io.helidon.media.common.spi.MediaSupportProvider; -import io.helidon.media.jsonp.common.JsonpProvider; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpProvider; +import io.helidon.media.jsonp.JsonpSupport; /** * JSON-P support common classes. * * @see JsonpSupport */ -module io.helidon.media.jsonp.common { +module io.helidon.media.jsonp { requires io.helidon.common; requires io.helidon.common.http; @@ -33,7 +33,7 @@ requires io.helidon.media.common; requires transitive java.json; - exports io.helidon.media.jsonp.common; + exports io.helidon.media.jsonp; provides MediaSupportProvider with JsonpProvider; } diff --git a/media/jsonp/common/src/main/resources/META-INF/native-image/io.helidon.media/helidon-media-jsonp-common/reflect-config.json b/media/jsonp/src/main/resources/META-INF/native-image/io.helidon.media/helidon-media-jsonp-common/reflect-config.json similarity index 100% rename from media/jsonp/common/src/main/resources/META-INF/native-image/io.helidon.media/helidon-media-jsonp-common/reflect-config.json rename to media/jsonp/src/main/resources/META-INF/native-image/io.helidon.media/helidon-media-jsonp-common/reflect-config.json diff --git a/media/jackson/common/src/main/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider b/media/jsonp/src/main/resources/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider similarity index 92% rename from media/jackson/common/src/main/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider rename to media/jsonp/src/main/resources/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider index c0149836ef4..db284e7d079 100644 --- a/media/jackson/common/src/main/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider +++ b/media/jsonp/src/main/resources/META-INF/services/io.helidon.media.common.spi.MediaSupportProvider @@ -14,4 +14,4 @@ # limitations under the License. # -io.helidon.media.jackson.common.JacksonProvider +io.helidon.media.jsonp.JsonpProvider diff --git a/media/jsonp/server/src/test/java/io/helidon/media/jsonp/server/JsonContentReaderTest.java b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonContentReaderTest.java similarity index 86% rename from media/jsonp/server/src/test/java/io/helidon/media/jsonp/server/JsonContentReaderTest.java rename to media/jsonp/src/test/java/io/helidon/media/jsonp/JsonContentReaderTest.java index 671f250349e..b42f1cf93bd 100644 --- a/media/jsonp/server/src/test/java/io/helidon/media/jsonp/server/JsonContentReaderTest.java +++ b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonContentReaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.helidon.media.jsonp.server; +package io.helidon.media.jsonp; import io.helidon.common.GenericType; import java.util.concurrent.CompletionStage; @@ -50,8 +50,7 @@ public class JsonContentReaderTest { public void simpleJsonObject() throws Exception { Publisher chunks = Multi.singleton("{ \"p\" : \"val\" }").map(s -> DataChunk.create(s.getBytes())); - CompletionStage stage = JsonSupport.create() - .reader() + CompletionStage stage = JsonpSupport.reader() .read(chunks, GenericType.create(JsonObject.class), CONTEXT) .toStage(); @@ -63,8 +62,7 @@ public void simpleJsonObject() throws Exception { public void incompatibleTypes() throws Exception { Publisher chunks = Multi.singleton("{ \"p\" : \"val\" }").map(s -> DataChunk.create(s.getBytes())); - CompletionStage stage = JsonSupport.create() - .reader() + CompletionStage stage = JsonpSupport.reader() .read(chunks, GenericType.create(JsonArray.class), CONTEXT) .toStage(); @@ -83,8 +81,7 @@ public void incompatibleTypes() throws Exception { public void simpleJsonArray() throws Exception { Publisher chunks = Multi.singleton("[ \"val\" ]").map(s -> DataChunk.create(s.getBytes())); - CompletionStage stage = JsonSupport.create() - .reader() + CompletionStage stage = JsonpSupport.reader() .read(chunks, GenericType.create(JsonArray.class), CONTEXT) .toStage(); @@ -96,8 +93,7 @@ public void simpleJsonArray() throws Exception { public void invalidJson() throws Exception { Publisher chunks = Multi.singleton("{ \"p\" : \"val\" ").map(s -> DataChunk.create(s.getBytes())); - CompletionStage stage = JsonSupport.create() - .reader() + CompletionStage stage = JsonpSupport.reader() .read(chunks, GenericType.create(JsonObject.class), CONTEXT) .toStage(); try { @@ -111,6 +107,6 @@ public void invalidJson() throws Exception { @Test public void defaultJsonSupportAsSingleton() { - assertThat(JsonSupport.create(), sameInstance(JsonSupport.create())); + assertThat(JsonpSupport.create(), sameInstance(JsonpSupport.create())); } } diff --git a/media/jsonp/server/src/test/java/io/helidon/media/jsonp/server/JsonSupportTest.java b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonSupportTest.java similarity index 86% rename from media/jsonp/server/src/test/java/io/helidon/media/jsonp/server/JsonSupportTest.java rename to media/jsonp/src/test/java/io/helidon/media/jsonp/JsonSupportTest.java index fa930d936ca..60d57bed37a 100644 --- a/media/jsonp/server/src/test/java/io/helidon/media/jsonp/server/JsonSupportTest.java +++ b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonSupportTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.media.jsonp.server; +package io.helidon.media.jsonp; import java.io.ByteArrayInputStream; import java.util.concurrent.TimeUnit; @@ -37,7 +37,7 @@ import static org.hamcrest.MatcherAssert.assertThat; /** - * Unit test for {@link JsonSupport}. + * Unit test for {@link JsonpSupport}. */ class JsonSupportTest { @@ -50,18 +50,17 @@ private JsonObject createJson() { @Test public void defaultJsonSupportAsSingleton() { - assertThat(JsonSupport.create(), is(sameInstance(JsonSupport.create()))); + assertThat(JsonpSupport.create(), is(sameInstance(JsonpSupport.create()))); } @Test public void pingPong() throws Exception { Routing routing = Routing.builder() - .register(JsonSupport.create()) .post("/foo", Handler.create(JsonObject.class, (req, res, json) -> res.send(json))) .build(); JsonObject json = createJson(); - TestResponse response = TestClient.create(routing) + TestResponse response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") .post(MediaPublisher.create( MediaType.APPLICATION_JSON.withCharset("UTF-8"), @@ -80,12 +79,11 @@ public void pingPong() throws Exception { @Test public void pingPongNoCharset() throws Exception { Routing routing = Routing.builder() - .register(JsonSupport.create()) .post("/foo", Handler.create(JsonObject.class, (req, res, json) -> res.send(json))) .build(); JsonObject json = createJson(); - TestResponse response = TestClient.create(routing) + TestResponse response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") .post(MediaPublisher.create(MediaType.APPLICATION_JSON, json.toString())); @@ -102,18 +100,16 @@ public void pingPongNoCharset() throws Exception { @Test public void invalidJson() throws Exception { Routing routing = Routing.builder() - .register(JsonSupport.create()) .post("/foo", Handler.create(JsonObject.class, (req, res, json) -> res.send(json))) .build(); - TestResponse response = TestClient.create(routing) + TestResponse response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") .post(MediaPublisher.create( MediaType.APPLICATION_JSON.withCharset("UTF-8"), "{ ... invalid ... }")); - assertThat(response.status(), - is(equalTo(Http.Status.INTERNAL_SERVER_ERROR_500))); + assertThat(response.status(), is(equalTo(Http.Status.INTERNAL_SERVER_ERROR_500))); } @Test @@ -131,21 +127,19 @@ public void explicitJsonSupportRegistrationMissingJsonProperty() MediaType.APPLICATION_JSON.withCharset("UTF-8"), json.toString())); - assertThat(response.status(), - is(equalTo(Http.Status.INTERNAL_SERVER_ERROR_500))); + assertThat(response.status(), is(equalTo(Http.Status.INTERNAL_SERVER_ERROR_500))); } @Test public void acceptHeaders() throws Exception { Routing routing = Routing.builder() - .register(JsonSupport.create()) .post("/foo", Handler.create(JsonObject.class, (req, res, json) -> res.send(json))) .build(); JsonObject json = createJson(); // Has accept - TestResponse response = TestClient.create(routing) + TestResponse response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") .header("Accept", "text/plain; q=.8, application/json; q=.1") .post(MediaPublisher.create( @@ -156,7 +150,7 @@ public void acceptHeaders() throws Exception { is(equalTo(MediaType.APPLICATION_JSON.toString()))); // Has accept with +json - response = TestClient.create(routing) + response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") .header("Accept", "text/plain; q=.8, application/specific+json; q=.1") .post(MediaPublisher.create( @@ -167,7 +161,7 @@ public void acceptHeaders() throws Exception { is(equalTo(MediaType.parse("application/specific+json").toString()))); // With start - response = TestClient.create(routing) + response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") .header("Accept", "text/plain; q=.8, application/*; q=.1") .post(MediaPublisher.create( @@ -178,7 +172,7 @@ public void acceptHeaders() throws Exception { is(equalTo(MediaType.APPLICATION_JSON.toString()))); // With JSONP standard application/javascript - response = TestClient.create(routing) + response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") .header("Accept", "application/javascript") .post(MediaPublisher.create( @@ -188,7 +182,7 @@ public void acceptHeaders() throws Exception { is(equalTo(Http.Status.INTERNAL_SERVER_ERROR_500))); // Without start - response = TestClient.create(routing) + response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") .header("Accept", "text/plain; q=.8, application/specific; q=.1") .post(MediaPublisher.create( diff --git a/media/jsonp/common/src/test/java/io/helidon/media/jsonp/common/JsonpReaderTest.java b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonpReaderTest.java similarity index 90% rename from media/jsonp/common/src/test/java/io/helidon/media/jsonp/common/JsonpReaderTest.java rename to media/jsonp/src/test/java/io/helidon/media/jsonp/JsonpReaderTest.java index 99942caab16..56e8200ec0f 100644 --- a/media/jsonp/common/src/test/java/io/helidon/media/jsonp/common/JsonpReaderTest.java +++ b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonpReaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,19 @@ * limitations under the License. */ -package io.helidon.media.jsonp.common; +package io.helidon.media.jsonp; -import io.helidon.common.GenericType; +import java.util.concurrent.ExecutionException; import javax.json.JsonArray; import javax.json.JsonObject; +import javax.json.JsonStructure; +import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.media.common.MessageBodyReaderContext; import io.helidon.common.reactive.Single; -import java.util.concurrent.ExecutionException; +import io.helidon.media.common.MessageBodyReader; +import io.helidon.media.common.MessageBodyReaderContext; import org.junit.jupiter.api.Test; @@ -41,7 +43,7 @@ public class JsonpReaderTest { private final static MessageBodyReaderContext CONTEXT = MessageBodyReaderContext.create(); - private final static JsonpBodyReader READER = JsonpSupport.create().newReader(); + private final static MessageBodyReader READER = JsonpSupport.reader(); @Test public void simpleJsonObject() throws Exception { diff --git a/media/jsonp/common/src/test/java/io/helidon/media/jsonp/common/JsonpStreamWriterTest.java b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonpStreamWriterTest.java similarity index 86% rename from media/jsonp/common/src/test/java/io/helidon/media/jsonp/common/JsonpStreamWriterTest.java rename to media/jsonp/src/test/java/io/helidon/media/jsonp/JsonpStreamWriterTest.java index 60c04dc502f..9af613eb720 100644 --- a/media/jsonp/common/src/test/java/io/helidon/media/jsonp/common/JsonpStreamWriterTest.java +++ b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonpStreamWriterTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.helidon.media.jsonp.common; +package io.helidon.media.jsonp; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -33,6 +33,8 @@ import io.helidon.common.http.DataChunk; import io.helidon.common.http.HashParameters; import io.helidon.common.reactive.Multi; +import io.helidon.media.common.MessageBodyOperator; +import io.helidon.media.common.MessageBodyStreamWriter; import io.helidon.media.common.MessageBodyWriterContext; import org.junit.jupiter.api.Test; @@ -50,7 +52,7 @@ public class JsonpStreamWriterTest { private static final JsonReaderFactory JSON_PARSER = Json.createReaderFactory(Map.of()); private static final MessageBodyWriterContext CONTEXT = MessageBodyWriterContext.create(HashParameters.create()); - private static final JsonpBodyStreamWriter WRITER = JsonpSupport.create().newStreamWriter(); + private static final JsonpBodyStreamWriter WRITER = (JsonpBodyStreamWriter) JsonpSupport.streamWriter(); private static final GenericType JSON_OBJECT = GenericType.create(JsonObject.class); private static final GenericType JSON_ARRAY = GenericType.create(JsonArray.class); private static final GenericType MY_TYPE = GenericType.create(JsonpStreamWriterTest.class); @@ -58,9 +60,15 @@ public class JsonpStreamWriterTest { @Test void testAcceptedTypes() { assertAll( - () -> assertThat("JsonObject accepted", WRITER.accept(JSON_OBJECT, CONTEXT), is(true)), - () -> assertThat("JsonArray accepted", WRITER.accept(JSON_ARRAY, CONTEXT), is(true)), - () -> assertThat("Pojo not accepted", WRITER.accept(MY_TYPE, CONTEXT), is(false)) + () -> assertThat("JsonObject accepted", + WRITER.accept(JSON_OBJECT, CONTEXT), + is(MessageBodyOperator.PredicateResult.SUPPORTED)), + () -> assertThat("JsonArray accepted", + WRITER.accept(JSON_ARRAY, CONTEXT), + is(MessageBodyOperator.PredicateResult.SUPPORTED)), + () -> assertThat("Pojo not accepted", + WRITER.accept(MY_TYPE, CONTEXT), + is(MessageBodyOperator.PredicateResult.NOT_SUPPORTED)) ); } diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java index 386f7ab3b68..077eefe4a42 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java @@ -32,8 +32,8 @@ private BodyPartBodyStreamReader() { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { - return BodyPart.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + return PredicateResult.supports(BodyPart.class, type); } @Override diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java index dfe778e392c..006c59068fa 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java @@ -35,8 +35,8 @@ private BodyPartBodyStreamWriter(String boundary) { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { - return WriteableBodyPart.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(WriteableBodyPart.class, type); } @Override diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartChunk.java b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartChunk.java index cd4de3d6ca6..ab3993b57ee 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartChunk.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartChunk.java @@ -26,7 +26,7 @@ final class BodyPartChunk implements DataChunk { private final DataChunk parent; - private final ByteBuffer data; + private final ByteBuffer[] data; /** * Create a new body part chunk instance. @@ -35,11 +35,11 @@ final class BodyPartChunk implements DataChunk { */ BodyPartChunk(ByteBuffer data, DataChunk parent) { this.parent = parent; - this.data = data; + this.data = new ByteBuffer[] {data}; } @Override - public ByteBuffer data() { + public ByteBuffer[] data() { return data; } diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/MimeParser.java b/media/multipart/src/main/java/io/helidon/media/multipart/MimeParser.java index 5316623b625..66563a22fd4 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/MimeParser.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/MimeParser.java @@ -299,7 +299,7 @@ private ParsingException(String message) { /** * Create a new exception with the specified cause. - * @param message exception cause + * @param cause exception cause */ private ParsingException(Throwable cause) { super(cause); @@ -773,7 +773,7 @@ private String readHeaderLine() { /** * Boyer-Moore search method. - * Copied from {@link java.util.regex.Pattern.java} + * Copied from {@link java.util.regex.Pattern} * * Pre calculates arrays needed to generate the bad character shift and the * good suffix shift. Only the last seven bits are used to see if chars @@ -821,10 +821,7 @@ private void compileBoundaryPattern() { /** * Finds the boundary in the given buffer using Boyer-Moore algorithm. - * Copied from {@link java.util.regex.Pattern.java} - * - * @param off start index in buf - * @param len number of bytes in buf + * Copied from {@link java.util.regex.Pattern} * * @return -1 if there is no match or index where the match starts */ diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyReader.java b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyReader.java index 48ee9c6ecbc..4907b416a4e 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyReader.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyReader.java @@ -41,8 +41,8 @@ private MultiPartBodyReader() { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext ctx) { - return MultiPart.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyReaderContext ctx) { + return PredicateResult.supports(MultiPart.class, type); } @Override @@ -55,7 +55,7 @@ public Single read(Publisher publisher, .orElseThrow(() -> new IllegalStateException("boundary header is missing")); MultiPartDecoder decoder = MultiPartDecoder.create(boundary, context); publisher.subscribe(decoder); - return (Single) Multi.from(decoder).collect(new PartsCollector()); + return (Single) Multi.create(decoder).collect(new PartsCollector()); } /** @@ -84,7 +84,7 @@ public void collect(ReadableBodyPart bodyPart) { // buffer the data // TODO support disk buffering with threshold - Publisher bufferedData = Single.from(ContentReaders.readBytes(content).toStage()) + Publisher bufferedData = Single.create(ContentReaders.readBytes(content).toStage()) .flatMap(BYTES_TO_CHUNKS); // create a content copy with the buffered data @@ -106,7 +106,7 @@ public ReadableMultiPart value() { } /** - * Implementation of {@link MultiMapper} that converts {@code byte[]} to a + * Implementation of {@link Mapper} that converts {@code byte[]} to a * publisher of {@link DataChunk} by copying the bytes. */ private static final class BytesToChunks implements Mapper> { diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java index e5e57b91526..223218c7468 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java @@ -43,8 +43,8 @@ private MultiPartBodyWriter(String boundary) { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { - return WriteableMultiPart.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(WriteableMultiPart.class, type); } @Override diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartDecoder.java b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartDecoder.java index 854f715987e..0860668a685 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartDecoder.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartDecoder.java @@ -101,8 +101,14 @@ public void onSubscribe(Subscription subscription) { @Override public void onNext(DataChunk chunk) { try { - int id = parser.offer(chunk.data()); - chunksByIds.put(id, chunk); + ByteBuffer[] byteBuffers = chunk.data(); + for (int i = 0; i < byteBuffers.length; i++) { + int id = parser.offer(byteBuffers[i]); + // record the chunk using the id of the last buffer + if (i == byteBuffers.length - 1) { + chunksByIds.put(id, chunk); + } + } parser.parse(); } catch (MimeParser.ParsingException ex) { emitter.fail(ex); @@ -240,7 +246,8 @@ private BodyPartChunk createPartChunk(BufferEntry entry) { if (chunk == null) { throw new IllegalStateException("Parent chunk not found, id=" + id); } - boolean release = data.limit() == chunk.data().limit(); + ByteBuffer[] originalBuffers = chunk.data(); + boolean release = data.limit() == originalBuffers[originalBuffers.length - 1].limit(); if (release) { chunksByIds.remove(id); } diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartEncoder.java b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartEncoder.java index a4b9d2b9dc5..c7f73e4ae81 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartEncoder.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartEncoder.java @@ -94,7 +94,7 @@ private void deferredInit() { emitter = BufferedEmittingPublisher.create(); // relay request to upstream, already reduced by flatmap emitter.onRequest((r, t) -> upstream.request(r)); - Multi.from(emitter) + Multi.create(emitter) .flatMap(Function.identity()) .onCompleteResume(DataChunk.create(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8))) .subscribe(downstream); diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/ReadableBodyPart.java b/media/multipart/src/main/java/io/helidon/media/multipart/ReadableBodyPart.java index a32f9b359cc..4dbf37acea0 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/ReadableBodyPart.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/ReadableBodyPart.java @@ -16,7 +16,6 @@ package io.helidon.media.multipart; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import io.helidon.media.common.MessageBodyReadableContent; @@ -51,7 +50,7 @@ public ReadableBodyPartHeaders headers() { * memory. When buffered, {@link #as(java.lang.Class)} can be called to * unmarshall the content synchronously. Otherwise, use {@link #content()} * and {@link MessageBodyReadableContent#as(Class)} to do it - * asynchronously with a {@link CompletionStage}. + * asynchronously with a {@link io.helidon.common.reactive.Single}. * * @return {@code true} if buffered, {@code false} otherwise */ diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderSubsBlackBoxTckTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderSubsBlackBoxTckTest.java index 64224e519cc..08e921489f3 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderSubsBlackBoxTckTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderSubsBlackBoxTckTest.java @@ -48,7 +48,7 @@ public DataChunk createElement(final int element) { @Override public Flow.Subscriber createFlowSubscriber() { MultiPartDecoder decoder = MultiPartDecoder.create("boundary", MEDIA_CONTEXT.readerContext()); - Multi.from(decoder).forEach(part -> {}); + Multi.create(decoder).forEach(part -> {}); return decoder; } diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderSubsWhiteBoxTckTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderSubsWhiteBoxTckTest.java index 1f7c5b2b41e..3321799f712 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderSubsWhiteBoxTckTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderSubsWhiteBoxTckTest.java @@ -86,7 +86,7 @@ public void onComplete() { } }; - Multi.from(decoder).forEach(part -> {}); + Multi.create(decoder).forEach(part -> {}); return decoder; } diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderTckTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderTckTest.java index 775b0934a65..8b61bc8cc88 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderTckTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderTckTest.java @@ -37,7 +37,7 @@ public MultiPartDecoderTckTest() { } static Flow.Publisher upstream(final long l) { - return Multi.from(LongStream.rangeClosed(1, l) + return Multi.create(LongStream.rangeClosed(1, l) .mapToObj(i -> { String chunk = ""; if (i == 1L) { diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderSubsBlackBoxTckTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderSubsBlackBoxTckTest.java index 3baa0356115..21296156d75 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderSubsBlackBoxTckTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderSubsBlackBoxTckTest.java @@ -35,7 +35,7 @@ protected MultiPartEncoderSubsBlackBoxTckTest() { @Override public Flow.Subscriber createFlowSubscriber() { MultiPartEncoder encoder = MultiPartEncoder.create("boundary", MEDIA_CONTEXT.writerContext()); - Multi.from(encoder).forEach(ch -> {}); + Multi.create(encoder).forEach(ch -> {}); return encoder; } diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderSubsWhiteBoxTckTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderSubsWhiteBoxTckTest.java index b31283fcab9..b150be94809 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderSubsWhiteBoxTckTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderSubsWhiteBoxTckTest.java @@ -81,7 +81,7 @@ public void onComplete() { } }; - Multi.from(encoder).forEach(ch -> {}); + Multi.create(encoder).forEach(ch -> {}); return encoder; } diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTckTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTckTest.java index b427b1aae45..4150f1ccbfd 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTckTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTckTest.java @@ -39,7 +39,7 @@ public MultiPartEncoderTckTest() { @Override public Flow.Publisher createFlowPublisher(final long l) { MultiPartEncoder encoder = MultiPartEncoder.create("boundary", MEDIA_CONTEXT.writerContext()); - Multi.from(LongStream.rangeClosed(1, l) + Multi.create(LongStream.rangeClosed(1, l) .mapToObj(i -> WriteableBodyPart.builder() .entity("part" + i) diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTest.java index de4732b1713..f506f110432 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTest.java @@ -100,7 +100,7 @@ public void testEncodeTwoParts() throws Exception { @Test public void testRequests() throws Exception { MultiPartEncoder enc = MultiPartEncoder.create("boundary", MEDIA_CONTEXT.writerContext()); - Multi.from(LongStream.range(1, 500) + Multi.create(LongStream.range(1, 500) .mapToObj(i -> WriteableBodyPart.builder() .entity("part" + i) diff --git a/messaging/connectors/pom.xml b/messaging/connectors/pom.xml deleted file mode 100644 index a039ceac59c..00000000000 --- a/messaging/connectors/pom.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - 4.0.0 - - io.helidon.messaging - helidon-messaging-project - 2.0.0-SNAPSHOT - - pom - io.helidon.messaging.connectors - helidon-messaging-connectors - Helidon Messaging Connectors - - - Helidon Messaging Connectors implementation - - - - kafka - - diff --git a/messaging/connectors/kafka/pom.xml b/messaging/kafka/pom.xml similarity index 80% rename from messaging/connectors/kafka/pom.xml rename to messaging/kafka/pom.xml index 1a537da6955..0e96f9a3741 100644 --- a/messaging/connectors/kafka/pom.xml +++ b/messaging/kafka/pom.xml @@ -23,26 +23,20 @@ 4.0.0 - io.helidon.messaging.connectors - helidon-messaging-connectors + io.helidon.messaging + helidon-messaging-project 2.0.0-SNAPSHOT - io.helidon.messaging.connectors.kafka - helidon-messaging-connectors-kafka + io.helidon.messaging.kafka + helidon-messaging-kafka jar - Helidon Messaging Connectors Kafka + Helidon Messaging Kafka Connector org.eclipse.microprofile.reactive.messaging microprofile-reactive-messaging-api - - - jakarta.enterprise - jakarta.enterprise.cdi-api - - io.helidon.config @@ -60,6 +54,10 @@ io.helidon.common helidon-common-configurable + + io.helidon.messaging + helidon-messaging + jakarta.enterprise jakarta.enterprise.cdi-api diff --git a/messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConfig.java b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConfig.java similarity index 100% rename from messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConfig.java rename to messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConfig.java diff --git a/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConfigBuilder.java b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConfigBuilder.java new file mode 100644 index 00000000000..10fe1b818ee --- /dev/null +++ b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConfigBuilder.java @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging.connectors.kafka; + +import io.helidon.messaging.ConnectorConfigBuilder; + +import org.apache.kafka.common.serialization.Deserializer; +import org.apache.kafka.common.serialization.Serializer; +import org.eclipse.microprofile.reactive.messaging.spi.ConnectorFactory; + +/** + * Build Kafka specific config. + */ +public final class KafkaConfigBuilder extends ConnectorConfigBuilder { + + KafkaConfigBuilder() { + super(); + super.property(ConnectorFactory.CONNECTOR_ATTRIBUTE, KafkaConnector.CONNECTOR_NAME); + } + + /** + * Add custom property. + * + * @param key property key + * @param value property value + * @return this builder + */ + public KafkaConfigBuilder property(String key, String value) { + super.property(key, value); + return this; + } + + /** + * A list of host/port pairs to use for establishing the initial connection to the Kafka cluster. + * The client will make use of all servers irrespective of which servers are specified here for + * bootstrapping—this list only impacts the initial hosts used to discover the full set of servers. + * This list should be in the form {@code host1:port1,host2:port2,....} + * Since these servers are just used for the initial connection to discover the full cluster + * membership (which may change dynamically), this list need not contain the full set of servers + * (you may want more than one, though, in case a server is down). + *

    + *
  • Type: list
  • + *
  • Default: ""
  • + *
  • Valid Values: non-null string
  • + *
+ * + * @param servers list of host/port pairs + * @return this builder + */ + public KafkaConfigBuilder bootstrapServers(String servers) { + super.property("bootstrap.servers", servers); + return this; + } + + /** + * Names of the topics to consume from. + * + * @param topics topic name + * @return this builder + */ + public KafkaConfigBuilder topic(String... topics) { + super.property("topic", String.join(",", topics)); + return this; + } + + /** + * A unique string that identifies the consumer group this consumer belongs to. + * This property is required. + * + *
    + *
  • Type: string
  • + *
+ * + * @param groupId consumer group identifier + * @return this builder + */ + public KafkaConfigBuilder groupId(String groupId) { + super.property("group.id", groupId); + return this; + } + + /** + * If true the consumer's offset will be periodically committed in the background. + * + *
    + *
  • Type: boolean
  • + *
  • Default: true
  • + *
+ * + * @param enableAutoCommit true for automatic offset committing + * @return this builder + */ + public KafkaConfigBuilder enableAutoCommit(boolean enableAutoCommit) { + super.property("enable.auto.commit", String.valueOf(enableAutoCommit)); + return this; + } + + /** + * Serializer class for key that implements the {@link org.apache.kafka.common.serialization.Serializer} interface. + * + * @param keySerializer class responsible for key serializing + * @return this builder + */ + public KafkaConfigBuilder keySerializer(Class> keySerializer) { + super.property("key.serializer", keySerializer.getName()); + return this; + } + + /** + * Deserializer class for key that implements the {@link org.apache.kafka.common.serialization.Deserializer} interface. + * + * @param keyDeserializer class responsible for key de-serializing + * @return this builder + */ + public KafkaConfigBuilder keyDeserializer(Class> keyDeserializer) { + super.property("key.deserializer", keyDeserializer.getName()); + return this; + } + + /** + * Serializer class for value that implements the {@link org.apache.kafka.common.serialization.Serializer} interface. + * + * @param valueSerializer class responsible for value serializing + * @return this builder + */ + public KafkaConfigBuilder valueSerializer(Class> valueSerializer) { + super.property("value.serializer", valueSerializer.getName()); + return this; + } + + /** + * Deserializer class for value that implements the {@link org.apache.kafka.common.serialization.Deserializer} interface. + * + * @param valueDeserializer class responsible for value de-serializing + * @return this builder + */ + public KafkaConfigBuilder valueDeserializer(Class> valueDeserializer) { + super.property("value.deserializer", valueDeserializer.getName()); + return this; + } + + /** + * The maximum time to block polling loop in milliseconds. + * + * @param pollTimeout time to block polling loop in milliseconds + * @return this builder + */ + public KafkaConfigBuilder pollTimeout(long pollTimeout) { + super.property("poll.timeout", String.valueOf(pollTimeout)); + return this; + } + + /** + * Period between successive executions of polling loop. + * + * @param periodExecutions in milliseconds + * @return this builder + */ + public KafkaConfigBuilder periodExecutions(long periodExecutions) { + super.property("period.executions", String.valueOf(periodExecutions)); + return this; + } + + /** + * What to do when there is no initial offset in Kafka or if the current offset does not exist any more on the server + * (e.g. because that data has been deleted): + * + *
    + *
  • earliest: automatically reset the offset to the earliest offset
  • + *
  • latest: automatically reset the offset to the latest offset
  • + *
  • none: throw exception to the consumer if no previous offset is found for the consumer's group
  • + *
+ *
    + *
  • Type: string
  • + *
  • Default: latest
  • + *
  • Valid Values: [latest, earliest, none]
  • + *
+ *

+ * + * @param autoOffsetReset [latest, earliest, none] + * @return this builder + */ + public KafkaConfigBuilder autoOffsetReset(AutoOffsetReset autoOffsetReset) { + super.property("auto.offset.reset", autoOffsetReset.name().toLowerCase()); + return this; + } + + /** + * What to do when there is no initial offset in Kafka. + */ + public enum AutoOffsetReset { + /** + * Automatically reset the offset to the earliest offset. + */ + LATEST, + /** + * Automatically reset the offset to the latest offset. + */ + EARLIEST, + /** + * Throw exception to the consumer if no previous offset is found for the consumer's group. + */ + NONE + } + + /** + * The producer will attempt to batch records together into fewer requests whenever multiple records + * are being sent to the same partition. This helps performance on both the client and the server. + * This configuration controls the default batch size in bytes. No attempt will be made to batch + * records larger than this size. Requests sent to brokers will contain multiple batches, + * one for each partition with data available to be sent. A small batch size will make batching + * less common and may reduce throughput (a batch size of zero will disable batching entirely). + * A very large batch size may use memory a bit more wastefully as we will always allocate + * a buffer of the specified batch size in anticipation of additional records. + * + *

    + *
  • Type: int
  • + *
  • Default: 16384
  • + *
+ * + * @param batchSize batch size in bytes + * @return this builder + */ + public KafkaConfigBuilder batchSize(int batchSize) { + super.property("batch.size", String.valueOf(batchSize)); + return this; + } + + /** + * The number of acknowledgments the producer requires the leader to have received before considering a request complete. + * This controls the durability of records that are sent. + *

+ * The following settings are allowed: + *

+ *
    + *
  • acks=0 If set to zero then the producer will not wait for any acknowledgment from the server at all. + * The record will be immediately added to the socket buffer and considered sent. + * No guarantee can be made that the server has received the record in this case, + * and the retries configuration will not take effect (as the client won't generally + * know of any failures). The offset given back for each record will always be set to -1.
  • + *
  • acks=1 This will mean the leader will write the record to its local log but will + * respond without awaiting full acknowledgement from all followers. In this case should + * the leader fail immediately after acknowledging the record but before the followers + * have replicated it then the record will be lost.
  • + *
  • acks=all This means the leader will wait for the full set of in-sync replicas + * to acknowledge the record. This guarantees that the record will not be lost + * as long as at least one in-sync replica remains alive. This is the strongest available guarantee. + * This is equivalent to the acks=-1 setting.
  • + * + *
+ *
    + *
  • Type: string
  • + *
  • Default: 1
  • + *
  • Valid Values: [all, -1, 0, 1]
  • + *
+ * + * @param acks acknowledge mode + * @return this builder + */ + public KafkaConfigBuilder acks(String acks) { + super.property("acks", acks); + return this; + } + + /** + * The total bytes of memory the producer can use to buffer records waiting to be sent to the server. + * If records are sent faster than they can be delivered to the server the producer will + * block for {@code max.block.ms} after which it will throw an exception. + * This setting should correspond roughly to the total memory the producer will use, + * but is not a hard bound since not all memory the producer uses is used for buffering. + * Some additional memory will be used for compression + * (if compression is enabled) as well as for maintaining in-flight requests. + * + *
    + *
  • Type: long
  • + *
  • Default: 33554432
  • + *
+ * + * @param bufferMemory bytes of memory + * @return this builder + */ + public KafkaConfigBuilder bufferMemory(long bufferMemory) { + super.property("buffer.memory", String.valueOf(bufferMemory)); + return this; + } + + /** + * The compression type for all data generated by the producer. The default is none (i.e. no compression). + * Valid values are none, gzip, snappy, lz4, or zstd. Compression is of full batches of data, + * so the efficacy of batching will also impact the compression ratio (more batching means better compression). + * + *
    + *
  • Type: string
  • + *
  • Default: none
  • + *
  • Valid Values: [none, gzip, snappy, lz4, zstd]
  • + *
+ * + * @param compressionType compression type + * @return this builder + */ + public KafkaConfigBuilder compressionType(String compressionType) { + super.property("compression.type", compressionType); + return this; + } + + /** + * Setting a value greater than zero will cause the client to resend any record whose send + * fails with a potentially transient error. Note that this retry is no different than + * if the client resent the record upon receiving the error. + * Allowing retries without setting {@code max.in.flight.requests.per.connection} to 1 will potentially + * change the ordering of records because if two batches are sent to a single partition, + * and the first fails and is retried but the second succeeds, + * then the records in the second batch may appear first. Note additionally that produce + * requests will be failed before the number of retries has been exhausted if the timeout + * configured by delivery.timeout.ms expires first before successful acknowledgement. + * Users should generally prefer to leave this config unset and instead use delivery.timeout.ms + * to control retry behavior. + * + *
    + *
  • Type: int
  • + *
  • Default: 2147483647
  • + *
+ * + * @param retries number of retries + * @return this builder + */ + public KafkaConfigBuilder retries(int retries) { + super.property("retries", String.valueOf(retries)); + return this; + } +} diff --git a/messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConnector.java b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConnector.java similarity index 89% rename from messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConnector.java rename to messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConnector.java index 16f9921f1ec..cbfa9f213fb 100644 --- a/messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConnector.java +++ b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConnector.java @@ -31,6 +31,7 @@ import io.helidon.common.configurable.ScheduledThreadPoolSupplier; import io.helidon.config.Config; import io.helidon.config.mp.MpConfig; +import io.helidon.messaging.Stoppable; import org.eclipse.microprofile.reactive.messaging.Message; import org.eclipse.microprofile.reactive.messaging.spi.Connector; @@ -45,7 +46,7 @@ */ @ApplicationScoped @Connector(KafkaConnector.CONNECTOR_NAME) -public class KafkaConnector implements IncomingConnectorFactory, OutgoingConnectorFactory { +public class KafkaConnector implements IncomingConnectorFactory, OutgoingConnectorFactory, Stoppable { private static final Logger LOGGER = Logger.getLogger(KafkaConnector.class.getName()); /** @@ -82,9 +83,10 @@ void terminate(@Observes @BeforeDestroyed(ApplicationScoped.class) Object event) /** * Gets the open resources for testing verification purposes. + * * @return the opened resources */ - Queue> resources(){ + Queue> resources() { return resources; } @@ -106,6 +108,7 @@ public SubscriberBuilder, Void> getSubscriberBuilder(org.ec /** * Creates a new instance of KafkaConnector with the required configuration. + * * @param config Helidon {@link io.helidon.config.Config config} * @return the new instance */ @@ -113,6 +116,15 @@ public static KafkaConnector create(Config config) { return new KafkaConnector(config); } + /** + * Creates a new instance of KafkaConnector with empty configuration. + * + * @return the new instance + */ + public static KafkaConnector create() { + return new KafkaConnector(Config.empty()); + } + /** * Stops the KafkaConnector and all the jobs and resources related to it. */ @@ -137,4 +149,14 @@ public void stop() { failed.forEach(e -> LOGGER.log(Level.SEVERE, "An error happened closing resource", e)); } } + + /** + * Custom config builder for Kafka connector. + * + * @return new Kafka specific config builder + */ + public static KafkaConfigBuilder configBuilder() { + return new KafkaConfigBuilder(); + } } + diff --git a/messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaMessage.java b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConsumerMessage.java similarity index 54% rename from messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaMessage.java rename to messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConsumerMessage.java index e3923164619..5201771d59c 100644 --- a/messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaMessage.java +++ b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaConsumerMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020 Oracle and/or 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,13 +16,15 @@ package io.helidon.messaging.connectors.kafka; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.eclipse.microprofile.reactive.messaging.Message; +import org.apache.kafka.common.header.Headers; /** * Kafka specific MP messaging message. @@ -30,30 +32,61 @@ * @param kafka record key type * @param kafka record value type */ -class KafkaMessage implements Message> { +class KafkaConsumerMessage implements KafkaMessage { - private final ConsumerRecord consumerRecord; private final CompletableFuture kafkaCommit; private final long millisWaitingTimeout; private final AtomicBoolean ack = new AtomicBoolean(); + private final ConsumerRecord consumerRecord; /** * Kafka specific MP messaging message. * - * @param consumerRecord obtained from Kafka topic - * @param kafkaCommit it will complete when Kafka commit is done. + * @param consumerRecord obtained from Kafka topic + * @param kafkaCommit it will complete when Kafka commit is done. * @param millisWaitingTimeout this is the time in milliseconds that the ack will be waiting - * the commit in Kafka. Applies only if autoCommit is false. + * the commit in Kafka. Applies only if autoCommit is false. */ - KafkaMessage(ConsumerRecord consumerRecord, CompletableFuture kafkaCommit, long millisWaitingTimeout) { + KafkaConsumerMessage(ConsumerRecord consumerRecord, CompletableFuture kafkaCommit, long millisWaitingTimeout) { + Objects.requireNonNull(consumerRecord); this.consumerRecord = consumerRecord; this.kafkaCommit = kafkaCommit; this.millisWaitingTimeout = millisWaitingTimeout; } @Override - public ConsumerRecord getPayload() { - return consumerRecord; + public Optional getTopic() { + return getConsumerRecord().map(ConsumerRecord::topic); + } + + @Override + public Optional getPartition() { + return getConsumerRecord().map(ConsumerRecord::partition); + } + + @Override + public Optional getOffset() { + return getConsumerRecord().map(ConsumerRecord::offset); + } + + @Override + public Headers getHeaders() { + return this.consumerRecord.headers(); + } + + @Override + public Optional> getConsumerRecord() { + return Optional.of(this.consumerRecord); + } + + @Override + public Optional getKey() { + return getConsumerRecord().map(ConsumerRecord::key); + } + + @Override + public V getPayload() { + return this.consumerRecord.value(); } @Override @@ -68,7 +101,10 @@ public C unwrap(Class unwrapType) { if (consumerRecord.getClass().isAssignableFrom(unwrapType)) { return (C) consumerRecord; } else { - throw new IllegalArgumentException("Can't unwrap to " + unwrapType.getName()); + throw new IllegalArgumentException("Can't unwrap " + + consumerRecord.getClass().getName() + + " to " + + unwrapType.getName()); } } @@ -78,11 +114,10 @@ boolean isAck() { @Override public String toString() { - return "KafkaMessage [consumerRecord=" + consumerRecord + ", ack=" + ack + "]"; + return "KafkaConsumerMessage [consumerRecord=" + consumerRecord + ", ack=" + ack + "]"; } - CompletableFuture kafkaCommit(){ + CompletableFuture kafkaCommit() { return kafkaCommit; } - } diff --git a/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaMessage.java b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaMessage.java new file mode 100644 index 00000000000..a9e0a5b2965 --- /dev/null +++ b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaMessage.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging.connectors.kafka; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.common.header.Headers; +import org.eclipse.microprofile.reactive.messaging.Message; + +/** + * Kafka specific Micro Profile Reactive Messaging Message. + * + * @param the type of Kafka record key + * @param the type of Kafka record value + */ +public interface KafkaMessage extends Message { + + /** + * Name of the topic from which was this message received. + * + * @return topic name + */ + Optional getTopic(); + + /** + * Number of partition from which was this message received. + * + * @return partition number + */ + Optional getPartition(); + + /** + * Offset of the record in partition from which was this message received. + * + * @return offset number + */ + Optional getOffset(); + + /** + * Returns {@link org.apache.kafka.clients.consumer.ConsumerRecord} if message was received from Kafka, + * otherwise return {@code Optional.empty()}. + * + * @return {@link org.apache.kafka.clients.consumer.ConsumerRecord} or {@code Optional.empty()} + */ + Optional> getConsumerRecord(); + + /** + * Key or {@code Optional.empty()} if non is specified. + * + * @return Key or {@code Optional.empty()} + */ + Optional getKey(); + + /** + * Returns {@link org.apache.kafka.common.header.Headers} received from Kafka with record + * or empty headers if message was not created by Kafka connector. + * + * @return {@link org.apache.kafka.common.header.Headers} received from Kafka + * or empty headers if message was not created by Kafka connector + */ + Headers getHeaders(); + + /** + * Create a message with the given payload and ack function. + * + * @param key Kafka record key + * @param payload Kafka record value + * @param ack The ack function, this will be invoked when the returned messages {@link #ack()} method is invoked + * @param the type of Kafka record key + * @param the type of Kafka record value + * @return A message with the given payload and ack function + */ + static KafkaMessage of(K key, V payload, Supplier> ack) { + Objects.requireNonNull(payload); + return new KafkaProducerMessage<>(key, payload, ack); + } + + /** + * Create a message with the given payload and ack function. + * + * @param payload Kafka record value + * @param ack The ack function, this will be invoked when the returned messages {@link #ack()} method is invoked + * @param the type of Kafka record key + * @param the type of Kafka record value + * @return A message with the given payload and ack function + */ + static KafkaMessage of(V payload, Supplier> ack) { + Objects.requireNonNull(payload); + return new KafkaProducerMessage<>(null, payload, ack); + } + + /** + * Create a message with the given payload and ack function. + * + * @param key Kafka record key + * @param payload Kafka record value + * @param the type of Kafka record key + * @param the type of Kafka record value + * @return A message with the given payload and ack function + */ + static KafkaMessage of(K key, V payload) { + Objects.requireNonNull(payload); + return new KafkaProducerMessage<>(key, payload, null); + } + + /** + * Create a message with the given payload and ack function. + * + * @param payload Kafka record value + * @param the type of Kafka record key + * @param the type of Kafka record value + * @return A message with the given payload and ack function + */ + static KafkaMessage of(V payload) { + Objects.requireNonNull(payload); + return new KafkaProducerMessage<>(null, payload, null); + } +} diff --git a/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaProducerMessage.java b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaProducerMessage.java new file mode 100644 index 00000000000..7694d2a4c90 --- /dev/null +++ b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaProducerMessage.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging.connectors.kafka; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.common.header.Headers; +import org.apache.kafka.common.header.internals.RecordHeaders; + +public class KafkaProducerMessage implements KafkaMessage { + + private final Headers headers; + private final K key; + private final V payload; + private final Supplier> ack; + + KafkaProducerMessage(K key, V payload, Supplier> ack) { + Objects.requireNonNull(payload); + this.key = key; + this.payload = payload; + this.ack = ack; + headers = new RecordHeaders(); + } + + @Override + public Optional getTopic() { + return Optional.empty(); + } + + @Override + public Optional getPartition() { + return Optional.empty(); + } + + @Override + public Optional getOffset() { + return Optional.empty(); + } + + @Override + public Optional> getConsumerRecord() { + return Optional.empty(); + } + + @Override + public Optional getKey() { + return Optional.ofNullable(key); + } + + @Override + public Headers getHeaders() { + return this.headers; + } + + @Override + public V getPayload() { + return this.payload; + } + + @Override + public CompletionStage ack() { + return ack.get(); + } +} diff --git a/messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaPublisher.java b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaPublisher.java similarity index 94% rename from messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaPublisher.java rename to messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaPublisher.java index e9112c41d9f..b947cf66e5d 100644 --- a/messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaPublisher.java +++ b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaPublisher.java @@ -73,7 +73,7 @@ public class KafkaPublisher implements Publisher> { private final Lock taskLock = new ReentrantLock(); private final Queue> backPressureBuffer = new LinkedList<>(); - private final Map>> pendingCommits = new HashMap<>(); + private final Map>> pendingCommits = new HashMap<>(); private final PartitionsAssignedLatch partitionsAssignedLatch = new PartitionsAssignedLatch(); private final ScheduledExecutorService scheduler; private final AtomicLong requests = new AtomicLong(); @@ -142,10 +142,10 @@ private void start() { for (long i = 0; i < eventsToEmit; i++) { ConsumerRecord cr = backPressureBuffer.poll(); CompletableFuture kafkaCommit = new CompletableFuture<>(); - KafkaMessage kafkaMessage = new KafkaMessage<>(cr, kafkaCommit, ackTimeout); + KafkaConsumerMessage kafkaMessage = + new KafkaConsumerMessage<>(cr, kafkaCommit, ackTimeout); if (!autoCommit) { - TopicPartition key = new TopicPartition(kafkaMessage.getPayload().topic(), - kafkaMessage.getPayload().partition()); + TopicPartition key = new TopicPartition(cr.topic(), cr.partition()); pendingCommits.computeIfAbsent(key, k -> new LinkedList<>()).add(kafkaMessage); } else { kafkaCommit.complete(null); @@ -180,7 +180,10 @@ private void start() { } private int currentNoAck() { - return pendingCommits.values().stream().map(list -> list.size()).reduce((a, b) -> a + b).orElse(0); + return pendingCommits.values().stream() + .map(List::size) + .reduce(Integer::sum) + .orElse(0); } /** @@ -190,15 +193,15 @@ private int currentNoAck() { */ private void processACK() { Map offsets = new LinkedHashMap<>(); - List> messagesToCommit = new LinkedList<>(); + List> messagesToCommit = new LinkedList<>(); // Commit highest offset + 1 of each partition that was ACK, and remove from pending - for (Entry>> entry : pendingCommits.entrySet()) { + for (Entry>> entry : pendingCommits.entrySet()) { // No need to sort it, offsets are consumed in order - List> byPartition = entry.getValue(); - Iterator> iterator = byPartition.iterator(); + List> byPartition = entry.getValue(); + Iterator> iterator = byPartition.iterator(); KafkaMessage highest = null; while (iterator.hasNext()) { - KafkaMessage element = iterator.next(); + KafkaConsumerMessage element = iterator.next(); if (element.isAck()) { messagesToCommit.add(element); highest = element; @@ -208,7 +211,7 @@ private void processACK() { } } if (highest != null) { - OffsetAndMetadata offset = new OffsetAndMetadata(highest.getPayload().offset() + 1); + OffsetAndMetadata offset = new OffsetAndMetadata(highest.getOffset().get() + 1); LOGGER.fine(() -> String.format("%s Will commit %s %s", topics, entry.getKey(), offset)); offsets.put(entry.getKey(), offset); } @@ -217,10 +220,10 @@ private void processACK() { LOGGER.fine(() -> String.format("%s Offsets %s", topics, offsets)); try { kafkaConsumer.commitSync(offsets); - messagesToCommit.stream().forEach(message -> message.kafkaCommit().complete(null)); + messagesToCommit.forEach(message -> message.kafkaCommit().complete(null)); } catch (RuntimeException e) { LOGGER.log(Level.SEVERE, "Unable to commit in Kafka " + offsets, e); - messagesToCommit.stream().forEach(message -> message.kafkaCommit().completeExceptionally(e)); + messagesToCommit.forEach(message -> message.kafkaCommit().completeExceptionally(e)); } } } diff --git a/messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaSubscriber.java b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaSubscriber.java similarity index 92% rename from messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaSubscriber.java rename to messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaSubscriber.java index 8a85e4c22cf..61ade45b749 100644 --- a/messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaSubscriber.java +++ b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/KafkaSubscriber.java @@ -80,7 +80,19 @@ public void onNext(Message message) { for (String topic : topics) { CompletableFuture completableFuture = new CompletableFuture<>(); futureList.add(completableFuture); - ProducerRecord record = new ProducerRecord<>(topic, message.getPayload()); + ProducerRecord record; + if (message instanceof KafkaMessage) { + KafkaMessage kafkaMessage = (KafkaMessage) message; + record = new ProducerRecord<>( + topic, + null, + null, + kafkaMessage.getKey().orElse(null), + kafkaMessage.getPayload(), + kafkaMessage.getHeaders()); + } else { + record = new ProducerRecord<>(topic, message.getPayload()); + } kafkaProducer.send(record, (metadata, exception) -> { if (exception != null) { subscription.cancel(); diff --git a/messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/PartitionsAssignedLatch.java b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/PartitionsAssignedLatch.java similarity index 100% rename from messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/PartitionsAssignedLatch.java rename to messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/PartitionsAssignedLatch.java diff --git a/messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/package-info.java b/messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/package-info.java similarity index 100% rename from messaging/connectors/kafka/src/main/java/io/helidon/messaging/connectors/kafka/package-info.java rename to messaging/kafka/src/main/java/io/helidon/messaging/connectors/kafka/package-info.java diff --git a/messaging/connectors/kafka/src/main/java/module-info.java b/messaging/kafka/src/main/java/module-info.java similarity index 94% rename from messaging/connectors/kafka/src/main/java/module-info.java rename to messaging/kafka/src/main/java/module-info.java index 4d555eeafe5..98342b59e10 100644 --- a/messaging/connectors/kafka/src/main/java/module-info.java +++ b/messaging/kafka/src/main/java/module-info.java @@ -26,7 +26,9 @@ requires transitive microprofile.reactive.messaging.api; requires transitive microprofile.reactive.streams.operators.api; requires io.helidon.common.context; + requires io.helidon.common.reactive; requires io.helidon.common.configurable; + requires io.helidon.messaging; requires microprofile.config.api; exports io.helidon.messaging.connectors.kafka; diff --git a/messaging/messaging/pom.xml b/messaging/messaging/pom.xml new file mode 100644 index 00000000000..90a4a26ec80 --- /dev/null +++ b/messaging/messaging/pom.xml @@ -0,0 +1,69 @@ + + + + + 4.0.0 + + io.helidon.messaging + helidon-messaging-project + 2.0.0-SNAPSHOT + + + helidon-messaging + Helidon Messaging + + + + io.helidon.config + helidon-config + + + io.helidon.config + helidon-config-mp + + + io.helidon.common + helidon-common-configurable + + + io.helidon.microprofile.reactive-streams + helidon-microprofile-reactive-streams + runtime + + + org.eclipse.microprofile.reactive-streams-operators + microprofile-reactive-streams-operators-api + + + org.eclipse.microprofile.reactive.messaging + microprofile-reactive-messaging-api + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + \ No newline at end of file diff --git a/messaging/messaging/src/main/java/io/helidon/messaging/Channel.java b/messaging/messaging/src/main/java/io/helidon/messaging/Channel.java new file mode 100644 index 00000000000..afd8c850f30 --- /dev/null +++ b/messaging/messaging/src/main/java/io/helidon/messaging/Channel.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.Objects; +import java.util.UUID; + +import io.helidon.config.Config; + +import org.eclipse.microprofile.reactive.messaging.Message; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +/** + * Channel representing publisher - subscriber relationship. + * + * @param payload type wrapped in {@link Message} being emitted by publisher and received by subscriber. + */ +public final class Channel { + private String name; + private Publisher> publisher; + private Subscriber> subscriber; + private Config publisherConfig; + private Config subscriberConfig; + + void connect() { + Objects.requireNonNull(publisher, "Missing publisher for channel " + name); + Objects.requireNonNull(subscriber, "Missing subscriber for channel " + name); + publisher.subscribe(subscriber); + } + + void setName(String name) { + this.name = name; + } + + Publisher> getPublisher() { + return publisher; + } + + @SuppressWarnings("unchecked") + void setPublisher(Publisher> publisher) { + this.publisher = (Publisher>) publisher; + } + + Subscriber> getSubscriber() { + return subscriber; + } + + @SuppressWarnings("unchecked") + void setSubscriber(Subscriber> subscriber) { + this.subscriber = (Subscriber>) subscriber; + } + + Config getPublisherConfig() { + return publisherConfig; + } + + Config getSubscriberConfig() { + return subscriberConfig; + } + + /** + * Channel name, used to pair configuration of connectors vs. channel configuration. + * + * @return channel name + */ + public String name() { + return name; + } + + /** + * Create new empty channel with given name. + * + * @param name channel name + * @param message payload type + * @return new channel + */ + public static Channel create(String name) { + return Channel.builder().name(name).build(); + } + + /** + * Create new empty channel with random name. + * + * @param message payload type + * @return new channel + */ + public static Channel create() { + return Channel.builder().build(); + } + + /** + * New builder for configuring new channel. + * + * @param message payload type + * @return channel builder + */ + public static Channel.Builder builder() { + return new Channel.Builder(); + } + + /** + * Channel builder. + * + * @param message payload type + */ + public static final class Builder implements io.helidon.common.Builder> { + + private final Channel channel = new Channel<>(); + + /** + * Channel name, used to pair configuration of connectors vs. channel configuration. + * + * @param name channel name + * @return this builder + */ + public Builder name(String name) { + channel.setName(name); + return this; + } + + /** + * Config available to publisher connector. + * + * @param config config supplied to publishing connector + * @return this builder + */ + public Builder publisherConfig(Config config) { + channel.publisherConfig = config; + return this; + } + + /** + * Config available to subscriber connector. + * + * @param config config supplied to subscribing connector + * @return this builder + */ + public Builder subscriberConfig(Config config) { + channel.subscriberConfig = config; + return this; + } + + @Override + public Channel build() { + if (channel.name == null) { + channel.name = UUID.randomUUID().toString(); + } + return channel; + } + + @Override + public Channel get() { + return channel; + } + } +} diff --git a/messaging/messaging/src/main/java/io/helidon/messaging/ConnectorConfigBuilder.java b/messaging/messaging/src/main/java/io/helidon/messaging/ConnectorConfigBuilder.java new file mode 100644 index 00000000000..09a5bace08a --- /dev/null +++ b/messaging/messaging/src/main/java/io/helidon/messaging/ConnectorConfigBuilder.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.HashMap; +import java.util.Map; + +import io.helidon.common.Builder; +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; + +/** + * Detached configuration of a single connector. + */ +public abstract class ConnectorConfigBuilder implements Builder { + private final Map configuration = new HashMap<>(); + + protected ConnectorConfigBuilder() { + } + + static ConnectorConfigBuilder create(Config config) { + ConnectorConfigBuilder result = new ConnectorConfigBuilder(){}; + result.config(config); + return result; + } + + protected ConnectorConfigBuilder property(String key, String value) { + configuration.put(key, value); + return this; + } + + ConnectorConfigBuilder config(Config configToPut) { + configuration.putAll(configToPut.detach().asMap().orElse(Map.of())); + return this; + } + + @Override + public Config build() { + return Config.builder(ConfigSources.create(configuration)) + .disableEnvironmentVariablesSource() + .disableSystemPropertiesSource() + .disableFilterServices() + .disableParserServices() + .build(); + } +} diff --git a/messaging/messaging/src/main/java/io/helidon/messaging/ConnectorConfigHelper.java b/messaging/messaging/src/main/java/io/helidon/messaging/ConnectorConfigHelper.java new file mode 100644 index 00000000000..c0d2d9bdd4e --- /dev/null +++ b/messaging/messaging/src/main/java/io/helidon/messaging/ConnectorConfigHelper.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.Map; +import java.util.stream.Collectors; + +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.config.ConfigValue; +import io.helidon.config.mp.MpConfigSources; +import io.helidon.config.spi.ConfigSource; + +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.eclipse.microprofile.reactive.messaging.spi.ConnectorFactory; + +final class ConnectorConfigHelper { + + private ConnectorConfigHelper() { + } + + static ConfigValue getIncomingConnectorName(Config config, String channelName) { + //Looks suspicious but incoming connector configured for outgoing channel is ok + return config.get(ConnectorFactory.OUTGOING_PREFIX) + .get(channelName) + .get(ConnectorFactory.CONNECTOR_ATTRIBUTE) + .asString(); + } + + static ConfigValue getOutgoingConnectorName(Config config, String channelName) { + //Looks suspicious but outgoing connector configured for incoming channel is ok + return config.get(ConnectorFactory.INCOMING_PREFIX) + .get(channelName) + .get(ConnectorFactory.CONNECTOR_ATTRIBUTE) + .asString(); + } + + static ConfigSource prefixedConfigSource(String prefix, Config config) { + return ConfigSources.create(config + .detach() + .asMap() + .orElse(Map.of()) + .entrySet() + .stream() + .collect(Collectors.toMap(e -> prefix + "." + e.getKey(), Map.Entry::getValue))) + .build(); + } + + static org.eclipse.microprofile.config.Config getConnectorConfig(String channelName, + String connectorName, + Config rootConfig) { + + Config incomingChannelConfig = rootConfig.get("mp.messaging.incoming"); + Config outgoingChannelConfig = rootConfig.get("mp.messaging.outgoing"); + + Config channelsConfig = (Config) ConnectorConfigBuilder + .create(incomingChannelConfig) + .config(outgoingChannelConfig) + .build(); + + Config channelConfig = channelsConfig + .get(channelName); + + ConfigValue configConnectorName = channelConfig + .get("connector") + .asString(); + + if (!configConnectorName.isPresent()) { + throw new MessagingException(String + .format("No connector configured for channel %s", channelName)); + } + if (!configConnectorName.get().equals(connectorName)) { + throw new MessagingException(String + .format("Connector name miss match for channel%s", channelName)); + } + + Config connectorConfig = rootConfig + .get("mp.messaging.connector") + .get(configConnectorName.get()); + + return ConfigProviderResolver + .instance() + .getBuilder() + .withSources( + MpConfigSources.create(ConnectorConfigBuilder + .create(connectorConfig) + .property(ConnectorFactory.CHANNEL_NAME_ATTRIBUTE, channelName) + .config(channelConfig) + .build())) + .build(); + } +} diff --git a/messaging/messaging/src/main/java/io/helidon/messaging/ContextSubscriber.java b/messaging/messaging/src/main/java/io/helidon/messaging/ContextSubscriber.java new file mode 100644 index 00000000000..569856a9404 --- /dev/null +++ b/messaging/messaging/src/main/java/io/helidon/messaging/ContextSubscriber.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.UUID; + +import io.helidon.common.context.Context; +import io.helidon.common.context.Contexts; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +class ContextSubscriber implements Subscriber { + + private final String prefix; + private final Subscriber subscriber; + + ContextSubscriber(final String prefix, final Subscriber subscriber) { + this.prefix = prefix; + this.subscriber = subscriber; + } + + static ContextSubscriber create(String prefix, Subscriber subscriber) { + return new ContextSubscriber<>(prefix, subscriber); + } + + @Override + public void onSubscribe(final Subscription s) { + subscriber.onSubscribe(s); + } + + @Override + public void onNext(final T item) { + runInNewContext(() -> subscriber.onNext(item)); + } + + @Override + public void onError(final Throwable t) { + runInNewContext(() -> subscriber.onError(t)); + } + + @Override + public void onComplete() { + runInNewContext(subscriber::onComplete); + } + + void runInNewContext(Runnable runnable) { + Context.Builder contextBuilder = Context.builder() + .id(String.format("%s-%s:", prefix, UUID.randomUUID().toString())); + Contexts.context().ifPresent(contextBuilder::parent); + Contexts.runInContext(contextBuilder.build(), runnable); + } +} diff --git a/messaging/messaging/src/main/java/io/helidon/messaging/Emitter.java b/messaging/messaging/src/main/java/io/helidon/messaging/Emitter.java new file mode 100644 index 00000000000..199fe12ef4d --- /dev/null +++ b/messaging/messaging/src/main/java/io/helidon/messaging/Emitter.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.concurrent.SubmissionPublisher; + +import org.eclipse.microprofile.reactive.messaging.Message; +import org.reactivestreams.FlowAdapters; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +/** + * Emitter is convenience publisher for one or multiple channels, + * publishing is as easy as calling {@link Emitter#send(Object)} method. + * + *
{@code
+ *  Channel simpleChannel = Channel.create();
+ *
+ *  Emitter emitter = Emitter.create(simpleChannel);
+ *
+ *  Messaging messaging = Messaging.builder()
+ *          .emitter(emitter)
+ *          .listener(simpleChannel, System.out::println)
+ *          .build();
+ *
+ *  messaging.start();
+ *
+ *  emitter.send(Message.of("Hello world!"));
+ * }
+ * + * @param message payload type + */ +public final class Emitter implements Publisher> { + + static final String EMITTER_CONTEXT_PREFIX = "emitter-message"; + private SubmissionPublisher> submissionPublisher; + private final Set> channels = new HashSet<>(); + + private Emitter() { + } + + void init(Executor executor, int maxBufferCapacity) { + submissionPublisher = new SubmissionPublisher<>(executor, maxBufferCapacity); + } + + /** + * Send raw payload to downstream, wrapped to {@link Message} when demand is higher than 0. + * Publishes the given item to each current subscriber by asynchronously invoking + * its onNext method, blocking uninterruptibly while resources for any subscriber + * are unavailable. + * + * @param msg payload to be wrapped and sent(or buffered if there is no demand) + * @return callback being invoked when message is acked + */ + public CompletionStage send(PAYLOAD msg) { + CompletableFuture future = new CompletableFuture<>(); + submissionPublisher.submit(Message.of(msg, () -> { + future.complete(null); + return CompletableFuture.completedStage(null); + })); + return future; + } + + /** + * Send {@link Message} to downstream when demand is higher than 0. Publishes the given item + * to each current subscriber by asynchronously invoking its onNext method, + * blocking uninterruptibly while resources for any subscriber are unavailable. + * + * @param msg message wrapper with payload + * @return estimate of the maximum lag (number of items submitted but not yet consumed) + * among all current subscribers. This value is at least one (accounting for this + * submitted item) if there are any subscribers, else zero. + * @throws IllegalStateException if emitter has been already completed + * @throws NullPointerException if message is null + * @throws java.util.concurrent.RejectedExecutionException if thrown by Executor + */ + public int send(Message msg) { + return submissionPublisher.submit(msg); + } + + /** + * Send onComplete signal to all subscribers. + */ + public void complete() { + submissionPublisher.close(); + } + + /** + * Send onError signal to all subscribers. + * + * @param e error to send in onError signal downstream + */ + public void error(Exception e) { + submissionPublisher.closeExceptionally(e); + } + + @Override + public void subscribe(final Subscriber> s) { + submissionPublisher.subscribe(FlowAdapters.toFlowSubscriber(ContextSubscriber.create(EMITTER_CONTEXT_PREFIX, s))); + } + + Set> channels() { + return this.channels; + } + + /** + * Create new Emitter to serve as a publisher for supplied channel. + * + * @param channel to serve as publisher in + * @param message payload type + * @return new emitter + */ + public static Emitter create(Channel channel) { + Emitter.Builder builder = Emitter.builder() + .channel(channel); + return builder.build(); + } + + /** + * Create new Emitter to serve as a broadcast publisher for supplied channels. + * + * @param channel to serve as publisher in + * @param channels to serve as publisher for + * @param message payload type + * @return new emitter + */ + public static Emitter create(Channel channel, Channel... channels) { + Emitter.Builder builder = Emitter.builder() + .channel(channel); + for (Channel ch : channels) { + builder.channel(ch); + } + return builder.build(); + } + + /** + * Prepare new builder for Emitter construction. + * + * @param message payload type + * @return new emitter builder + */ + public static Emitter.Builder builder() { + return new Emitter.Builder(); + } + + /** + * Builder for {@link io.helidon.messaging.Emitter}. + * + * @param message payload type + */ + public static final class Builder implements io.helidon.common.Builder> { + + private final Emitter emitter = new Emitter<>(); + + /** + * Add new {@link io.helidon.messaging.Channel} for Emitter to publish to. + * + * @param channel to serve as publisher in + * @return this builder + */ + public Builder channel(Channel channel) { + emitter.channels.add(channel); + return this; + } + + @Override + public Emitter build() { + return emitter; + } + + @Override + public Emitter get() { + return emitter; + } + } +} diff --git a/messaging/messaging/src/main/java/io/helidon/messaging/Messaging.java b/messaging/messaging/src/main/java/io/helidon/messaging/Messaging.java new file mode 100644 index 00000000000..3f7f73e33d0 --- /dev/null +++ b/messaging/messaging/src/main/java/io/helidon/messaging/Messaging.java @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.concurrent.Flow; +import java.util.function.Consumer; +import java.util.function.Function; + +import io.helidon.common.HelidonFeatures; +import io.helidon.common.HelidonFlavor; +import io.helidon.common.reactive.Multi; +import io.helidon.config.Config; + +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.spi.ConnectorFactory; +import org.eclipse.microprofile.reactive.messaging.spi.IncomingConnectorFactory; +import org.eclipse.microprofile.reactive.messaging.spi.OutgoingConnectorFactory; +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.FlowAdapters; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +/** + * Helidon Reactive Messaging. + */ +public interface Messaging { + + /** + * Connect all channels and start streaming. + * + * @return started messaging + */ + Messaging start(); + + /** + * Invoke stop method in all connectors implementing it. Stopped messaging cannot be started again. + */ + void stop(); + + /** + * Create builder for constructing new Messaging. + * + * @return new builder + */ + static Builder builder() { + return new Builder(); + } + + final class Builder implements io.helidon.common.Builder { + + static { + HelidonFeatures.register(HelidonFlavor.SE, "Messaging"); + } + + private final MessagingImpl messaging; + + private Builder() { + messaging = new MessagingImpl(); + } + + /** + * Configuration needed for configuring connector and their routing. + * + * @param config config for connectors and their routes. + * @return this builder + */ + public Builder config(Config config) { + messaging.setConfig(config); + return this; + } + + /** + * Add connector implementing {@link IncomingConnectorFactory}, {@link OutgoingConnectorFactory} or both. + * + * @param connector connector to add. + * @return this builder + */ + public Builder connector(ConnectorFactory connector) { + if (connector instanceof IncomingConnectorFactory) { + this.messaging.addIncomingConnector((IncomingConnectorFactory) connector); + } + if (connector instanceof IncomingConnectorFactory) { + this.messaging.addOutgoingConnector((OutgoingConnectorFactory) connector); + } + return this; + } + + /** + * Register new emitter and all its channels. + * + * @param emitter to be registered + * @param message payload type + * @return this builder + */ + public Builder emitter(Emitter emitter) { + messaging.addEmitter(emitter); + for (Channel ch : emitter.channels()) { + this.messaging.registerChannel(ch); + ch.setPublisher(emitter); + } + return this; + } + + /** + * Register {@link PublisherBuilder} to be used for construction of the publisher for supplied {@link Channel}. + * + * @param channel to be publisher constructed for + * @param publisherBuilder to be used for construction of the publisher + * @param message payload type + * @return this builder + */ + public Builder publisher(Channel channel, + PublisherBuilder> publisherBuilder) { + return this.publisher(channel, publisherBuilder.buildRs()); + } + + /** + * Register {@link Publisher} to be used for supplied {@link Channel}. + * + * @param channel to use publisher in + * @param publisher to publish in supplied channel + * @param wrapper function to be used for raw payload wrapping, + * if null simple {@link Message#of(Object)} is used. + * @param message payload type + * @return this builder + */ + public Builder publisher(Channel channel, + Publisher publisher, + Function> wrapper) { + if (wrapper == null) { + wrapper = Message::of; + } + return this.publisher(channel, ReactiveStreams.fromPublisher(publisher).map(wrapper).buildRs()); + } + + /** + * Register {@link Flow.Publisher} to be used for supplied {@link Channel}. + * + * @param channel to use publisher in + * @param publisher to publish in supplied channel + * @param wrapper function to be used for raw payload wrapping, + * if null simple {@link Message#of(Object)} is used. + * @param message payload type + * @return this builder + */ + public Builder publisher(Channel channel, + Flow.Publisher publisher, + Function> wrapper) { + if (wrapper == null) { + wrapper = Message::of; + } + return this.publisher(channel, FlowAdapters.toPublisher(publisher), wrapper); + } + + /** + * Register {@link Flow.Publisher} to be used for supplied {@link Channel}. + * + * @param channel to use publisher in + * @param publisher to publish in supplied channel + * @param message payload type + * @return this builder + */ + public Builder publisher(Channel channel, + Flow.Publisher> publisher) { + return publisher(channel, FlowAdapters.toPublisher(publisher)); + } + + /** + * Register {@link Publisher} to be used for supplied {@link Channel}. + * + * @param channel to use publisher in + * @param publisher to publish in supplied channel + * @param message payload type + * @return this builder + */ + public Builder publisher(Channel channel, + Publisher> publisher) { + this.messaging.registerChannel(channel); + channel.setPublisher(publisher); + return this; + } + + /** + * Register {@link java.util.function.Consumer} for listening every payload coming from upstream. + * This listener creates unbounded({@link Long#MAX_VALUE}) demand. + * {@link org.eclipse.microprofile.reactive.messaging.Message}s are automatically acked and unwrapped. + * Equivalent of + * {@link org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder#forEach(java.util.function.Consumer)}. + * + * @param channel to use subscriber in + * @param consumer to consume payloads + * @param message payload type + * @return this builder + */ + public Builder listener(Channel channel, + Consumer consumer) { + this.messaging.registerChannel(channel); + channel.setSubscriber(Builder.unwrapProcessorBuilder() + .forEach(consumer) + .build()); + return this; + } + + /** + * Register {@link Flow.Subscriber} to be used for supplied {@link Channel}. + * + * @param channel to use subscriber in + * @param subscriber to subscribe to supplied channel + * @param message payload type + * @return this builder + */ + public Builder subscriber(Channel channel, + Flow.Subscriber> subscriber) { + this.subscriber(channel, FlowAdapters.toSubscriber(subscriber)); + return this; + } + + /** + * Use provided {@link Multi} to subscribe to supplied {@link Channel}. + * + * @param channel to use subscriber in + * @param consumer to subscribe to supplied channel + * @param message payload type + * @return this builder + */ + public Builder subscriber(Channel channel, + Consumer>> consumer) { + Processor, ? extends Message> processor = + ReactiveStreams.>builder().buildRs(); + consumer.accept(Multi.create(FlowAdapters.toFlowPublisher(processor))); + this.subscriber(channel, processor); + return this; + } + + /** + * Register {@link SubscriberBuilder} to be used for creating {@link Subscriber} for supplied {@link Channel}. + * + * @param channel to use subscriber in + * @param subscriberBuilder to subscribe to supplied channel + * @param message payload type + * @param result type + * @return this builder + */ + public Builder subscriber( + Channel channel, + SubscriberBuilder, RESULT> subscriberBuilder) { + this.subscriber(channel, subscriberBuilder.build()); + return this; + } + + /** + * Register {@link Subscriber} to be used for supplied {@link Channel}. + * + * @param channel to use subscriber in + * @param subscriber to subscribe to supplied channel + * @param message payload type + * @return this builder + */ + public Builder subscriber(Channel channel, + Subscriber> subscriber) { + this.messaging.registerChannel(channel); + channel.setSubscriber(subscriber); + return this; + } + + /** + * Register {@link Processor} to be used is {@code in} {@link Channel}'s subscriber + * and {@code out} {@link Channel}'s publisher. + * + * @param in {@link Channel} to use supplied {@link Processor} as subscriber in + * @param out {@link Channel} to use supplied {@link Processor} as publisher in + * @param processor to be used between supplied channels + * @param message payload type of in channel + * @param message payload type of out channel + * @return this builder + */ + public Builder processor( + Channel in, + Channel out, + Processor, ? extends Message> processor) { + + this.messaging.registerChannel(in); + this.messaging.registerChannel(out); + in.setSubscriber(processor); + out.setPublisher(processor); + return this; + } + + /** + * Register {@link ProcessorBuilder} for building {@link Processor} + * to be used is {@code in} {@link Channel}'s subscriber and {@code out} {@link Channel}'s publisher. + * + * @param in {@link Channel} to use supplied {@link Processor} as subscriber in + * @param out {@link Channel} to use supplied {@link Processor} as publisher in + * @param processorBuilder to be used between supplied channels + * @param message payload type of in channel + * @param message payload type of out channel + * @return this builder + */ + public Builder processor( + Channel in, Channel out, + ProcessorBuilder, ? extends Message> processorBuilder) { + + return processor(in, out, processorBuilder.buildRs()); + } + + /** + * Register a mapping function between two channels. + * + * @param in {@link Channel} to map from + * @param out {@link Channel} to map to + * @param messageFunction mapping function + * @param message payload type of in channel + * @param message payload type of out channel + * @return this builder + */ + public Builder processor(Channel in, Channel out, + Function messageFunction) { + + Processor, ? extends Message> processor = + Builder.unwrapProcessorBuilder() + .map(messageFunction) + .via(Builder.wrapProcessorBuilder()) + .buildRs(); + + return processor(in, out, processor); + } + + /** + * Build new {@link io.helidon.messaging.Messaging} instance. + * + * @return new instance of {@link io.helidon.messaging.Messaging} + */ + public Messaging build() { + return messaging; + } + + private static ProcessorBuilder> wrapProcessorBuilder() { + return ReactiveStreams.builder() + .map(Message::of); + } + + private static ProcessorBuilder, ? extends PAYLOAD> + unwrapProcessorBuilder() { + return ReactiveStreams.>builder() + .peek(Message::ack) + .map(Message::getPayload); + } + } + + +} diff --git a/media/jsonb/server/src/main/java/module-info.java b/messaging/messaging/src/main/java/io/helidon/messaging/MessagingException.java similarity index 59% rename from media/jsonb/server/src/main/java/module-info.java rename to messaging/messaging/src/main/java/io/helidon/messaging/MessagingException.java index 7e46f055746..8c4b28b566a 100644 --- a/media/jsonb/server/src/main/java/module-info.java +++ b/messaging/messaging/src/main/java/io/helidon/messaging/MessagingException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,16 +12,22 @@ * 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.messaging; + /** - * JSON-B support for Webserver. - * - * @see io.helidon.media.jsonb.server.JsonBindingSupport + * Reactive Messaging specific exception. */ -module io.helidon.media.jsonb.server { - requires io.helidon.media.jsonb.common; - requires io.helidon.webserver; +public class MessagingException extends RuntimeException { - exports io.helidon.media.jsonb.server; + /** + * Create new MessagingException with supplied message. + * + * @param message supplied message + */ + public MessagingException(final String message) { + super(message); + } } diff --git a/messaging/messaging/src/main/java/io/helidon/messaging/MessagingImpl.java b/messaging/messaging/src/main/java/io/helidon/messaging/MessagingImpl.java new file mode 100644 index 00000000000..fda1bea1b64 --- /dev/null +++ b/messaging/messaging/src/main/java/io/helidon/messaging/MessagingImpl.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Flow; +import java.util.concurrent.atomic.AtomicInteger; + +import io.helidon.common.configurable.ThreadPoolSupplier; +import io.helidon.common.reactive.Multi; +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.config.ConfigValue; + +import org.eclipse.microprofile.reactive.messaging.spi.Connector; +import org.eclipse.microprofile.reactive.messaging.spi.ConnectorFactory; +import org.eclipse.microprofile.reactive.messaging.spi.IncomingConnectorFactory; +import org.eclipse.microprofile.reactive.messaging.spi.OutgoingConnectorFactory; + +class MessagingImpl implements Messaging { + + private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(); + private final int instanceNumber; + private final Set> emitters = new HashSet<>(); + private final Map> channelMap = new HashMap<>(); + private final Map incomingConnectors = new HashMap<>(); + private final Map outgoingConnectors = new HashMap<>(); + private Config config; + private MessagingImpl.State state = MessagingImpl.State.INIT; + private ThreadPoolSupplier threadPoolSupplier; + + MessagingImpl() { + this.instanceNumber = INSTANCE_COUNTER.incrementAndGet(); + } + + @Override + public Messaging start() { + state.start(this); + if (!emitters.isEmpty()) { + threadPoolSupplier = ThreadPoolSupplier.builder() + .threadNamePrefix("helidon-messaging-" + instanceNumber + "-") + .build(); + emitters.forEach(emitter -> emitter.init(threadPoolSupplier.get(), Flow.defaultBufferSize())); + } + channelMap.values().forEach(this::findConnectors); + channelMap.values().forEach(Channel::connect); + return this; + } + + @Override + public void stop() { + state.stop(this); + Multi.concat( + Multi.create(incomingConnectors.values()).map(Object.class::cast), + Multi.create(outgoingConnectors.values()).map(Object.class::cast)) + .distinct() + .filter(Stoppable.class::isInstance) + .map(Stoppable.class::cast) + .forEach(Stoppable::stop); + if (!emitters.isEmpty()) { + emitters.forEach(Emitter::complete); + threadPoolSupplier.get().shutdown(); + } + } + + void setConfig(final Config config) { + this.config = config; + } + + void addIncomingConnector(IncomingConnectorFactory connector) { + incomingConnectors.put(getConnectorName(connector.getClass()), connector); + } + + void addOutgoingConnector(OutgoingConnectorFactory connector) { + outgoingConnectors.put(getConnectorName(connector.getClass()), connector); + } + + void addEmitter(Emitter emitter) { + emitters.add(emitter); + } + + void registerChannel(Channel channel) { + Channel ch = channelMap.get(channel.name()); + if (ch == null) { + ch = channel; + channelMap.put(ch.name(), ch); + } + } + + private String getConnectorName(Class clazz) { + Connector annotation = clazz.getAnnotation(Connector.class); + if (annotation == null) { + throw new MessagingException("Missing @Connector annotation in provided " + clazz.getSimpleName()); + } + return annotation.value(); + } + + private void findConnectors(Channel channel) { + Config.Builder configBuilder = Config.builder() + .disableSystemPropertiesSource() + .disableEnvironmentVariablesSource(); + if (config != null) { + configBuilder.addSource(ConfigSources.create(config)); + } + + if (channel.getPublisherConfig() != null) { + configBuilder + .addSource(ConnectorConfigHelper + .prefixedConfigSource(ConnectorFactory.OUTGOING_PREFIX + channel.name(), + channel.getPublisherConfig())); + } + + if (channel.getSubscriberConfig() != null) { + configBuilder + .addSource(ConnectorConfigHelper + .prefixedConfigSource(ConnectorFactory.INCOMING_PREFIX + channel.name(), + channel.getSubscriberConfig())); + } + Config mergedConfig = configBuilder.build(); + + ConfigValue incomingConnectorName = ConnectorConfigHelper.getIncomingConnectorName(mergedConfig, channel.name()); + ConfigValue outgoingConnectorName = ConnectorConfigHelper.getOutgoingConnectorName(mergedConfig, channel.name()); + + if (incomingConnectorName.isPresent()) { + String connectorName = incomingConnectorName.get(); + org.eclipse.microprofile.config.Config incomingConnectorConfig = + ConnectorConfigHelper.getConnectorConfig(channel.name(), connectorName, mergedConfig); + channel.setPublisher( + incomingConnectors.get(connectorName) + .getPublisherBuilder(incomingConnectorConfig) + .buildRs()); + } + if (outgoingConnectorName.isPresent()) { + String connectorName = outgoingConnectorName.get(); + org.eclipse.microprofile.config.Config outgoingConnectorConfig = + ConnectorConfigHelper.getConnectorConfig(channel.name(), connectorName, mergedConfig); + channel.setSubscriber( + outgoingConnectors.get(connectorName) + .getSubscriberBuilder(outgoingConnectorConfig) + .build()); + } + } + + enum State { + INIT { + void start(MessagingImpl m) { + m.state = STARTED; + } + + void stop(MessagingImpl m) { + throw new MessagingException("Messaging is not started yet!"); + } + }, + STARTED { + void start(MessagingImpl m) { + throw new MessagingException("Messaging has been started already!"); + } + + void stop(MessagingImpl m) { + m.state = STOPPED; + } + }, + STOPPED { + void start(MessagingImpl m) { + throw new MessagingException("Messaging has been stopped already!"); + } + + void stop(MessagingImpl m) { + start(m); + } + }; + + abstract void start(MessagingImpl m); + + abstract void stop(MessagingImpl m); + } +} diff --git a/common/metrics/src/main/java/module-info.java b/messaging/messaging/src/main/java/io/helidon/messaging/Stoppable.java similarity index 66% rename from common/metrics/src/main/java/module-info.java rename to messaging/messaging/src/main/java/io/helidon/messaging/Stoppable.java index c78b1a1800d..cdaa87400e6 100644 --- a/common/metrics/src/main/java/module-info.java +++ b/messaging/messaging/src/main/java/io/helidon/messaging/Stoppable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or 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,14 +15,15 @@ * */ -module io.helidon.common.metrics { - - requires io.helidon.config; - requires transitive microprofile.metrics.api; - - exports io.helidon.common.metrics; - - uses io.helidon.common.metrics.InternalBridge; +package io.helidon.messaging; +/** + * Messaging connector which can be notified about messaging stop. + */ +public interface Stoppable { + /** + * Invoked during {@link Messaging#stop()}. + */ + void stop(); } diff --git a/messaging/messaging/src/main/java/io/helidon/messaging/package-info.java b/messaging/messaging/src/main/java/io/helidon/messaging/package-info.java new file mode 100644 index 00000000000..fb955f6e51c --- /dev/null +++ b/messaging/messaging/src/main/java/io/helidon/messaging/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Helidon Reactive Messaging. + */ +package io.helidon.messaging; diff --git a/messaging/messaging/src/main/java/module-info.java b/messaging/messaging/src/main/java/module-info.java new file mode 100644 index 00000000000..db1efaf827f --- /dev/null +++ b/messaging/messaging/src/main/java/module-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module io.helidon.messaging { + requires java.logging; + + requires io.helidon.common.context; + requires io.helidon.common.configurable; + requires io.helidon.config.mp; + requires transitive io.helidon.config; + requires transitive org.reactivestreams; + requires transitive microprofile.config.api; + requires transitive microprofile.reactive.messaging.api; + requires transitive microprofile.reactive.streams.operators.api; + + exports io.helidon.messaging; +} \ No newline at end of file diff --git a/messaging/messaging/src/test/java/io/helidon/messaging/CovarianceTest.java b/messaging/messaging/src/test/java/io/helidon/messaging/CovarianceTest.java new file mode 100644 index 00000000000..df615929412 --- /dev/null +++ b/messaging/messaging/src/test/java/io/helidon/messaging/CovarianceTest.java @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import io.helidon.common.reactive.Multi; + +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.junit.jupiter.api.Test; + +public class CovarianceTest { + @Test + void flowPublisherWithWrapper() { + List testValues = List.of("test1", "test2", "test3", "test4"); + + LatchedTestData testData = new LatchedTestData<>( + testValues.stream() + .map(CoolerString::new) + .collect(Collectors.toList()) + ); + + Channel channel1 = Channel.create(); + Channel channel2 = Channel.create(); + Channel channel3 = Channel.create(); + Channel channel4 = Channel.create(); + + Messaging messaging = Messaging.builder() + .publisher(channel1, Multi.just(testData.expected.get(0)), + (Object s) -> Message.of((CoolerString) s)) + .publisher(channel2, Multi.just(testData.expected.get(1)), + (CharSequence s) -> Message.of((CoolerString) s)) + .publisher(channel3, Multi.just(testData.expected.get(2)), + Message::of) + .publisher(channel4, Multi.just((CoolerString) testData.expected.get(3)), + (Object s) -> Message.of((CoolerString) s)) + .listener(channel1, testData::add) + .listener(channel2, testData::add) + .listener(channel3, charSequence -> testData.add((CoolerString) charSequence)) + .listener(channel4, coolString -> testData.add((CoolerString) coolString)) + .build() + .start(); + + testData.assertEqualsAnyOrder(); + + messaging.stop(); + } + + @Test + void publisherWithWrapper() { + List testValues = List.of("test1", "test2", "test3", "test4"); + + LatchedTestData testData = new LatchedTestData<>( + testValues.stream() + .map(CoolerString::new) + .collect(Collectors.toList()) + ); + + Channel channel1 = Channel.create(); + Channel channel2 = Channel.create(); + Channel channel3 = Channel.create(); + Channel channel4 = Channel.create(); + + Messaging messaging = Messaging.builder() + .publisher(channel1, ReactiveStreams.of(testData.expected.get(0)).buildRs(), + (Object s) -> Message.of((CoolerString) s)) + .publisher(channel2, ReactiveStreams.of(testData.expected.get(1)).buildRs(), + (CharSequence s) -> Message.of((CoolerString) s)) + .publisher(channel3, ReactiveStreams.of(testData.expected.get(2)).buildRs(), + Message::of) + .publisher(channel4, ReactiveStreams.of((CoolerString) testData.expected.get(3)).buildRs(), + (Object s) -> Message.of((CoolerString) s)) + .listener(channel1, testData::add) + .listener(channel2, testData::add) + .listener(channel3, charSequence -> testData.add((CoolerString) charSequence)) + .listener(channel4, coolString -> testData.add((CoolerString) coolString)) + .build() + .start(); + + testData.assertEqualsAnyOrder(); + + messaging.stop(); + } + + @Test + void publisherBuilder() { + List testValues = List.of("test1", "test2"); + + LatchedTestData testData = new LatchedTestData<>( + testValues.stream() + .map(CoolestString::new) + .collect(Collectors.toList()) + ); + + Channel channel1 = Channel.create(); + Channel channel2 = Channel.create(); + + Messaging messaging = Messaging.builder() + .publisher(channel1, ReactiveStreams.of(testData.expected.get(0)).map(Message::of)) + .publisher(channel2, ReactiveStreams.of((CoolestString) testData.expected.get(1)).map(Message::of)) + .listener(channel1, (CharSequence charSequence) -> testData.add((CoolestString) charSequence)) + .listener(channel2, coolString -> testData.add((CoolestString) coolString)) + .build() + .start(); + + testData.assertEqualsAnyOrder(); + + messaging.stop(); + } + + + @Test + void processorFunction() { + List testValues = List.of("test1"); + + LatchedTestData testData = new LatchedTestData<>( + testValues.stream() + .map(CoolestString::new) + .collect(Collectors.toList()) + ); + + Channel channel1 = Channel.create(); + Channel channel2 = Channel.create(); + Channel channel3 = Channel.create(); + Channel channel4 = Channel.create(); + Channel channel5 = Channel.create(); + Channel channel6 = Channel.create(); + + Messaging messaging = Messaging.builder() + .publisher(channel1, ReactiveStreams.of(testData.expected.get(0)).map(Message::of)) + .processor(channel1, channel2, s -> s) + .processor(channel2, channel3, s -> s) + .processor(channel3, channel4, (CoolString s) -> (CoolerString) s) + .processor(channel4, channel5, s -> (CoolestString) s) + .processor(channel5, channel6, s -> s) + .listener(channel6, coolString -> testData.add((CoolestString) coolString)) + .build() + .start(); + + testData.assertEqualsAnyOrder(); + + messaging.stop(); + } + + @Test + void processorBuilder() { + List testValues = List.of("test1"); + + LatchedTestData testData = new LatchedTestData<>( + testValues.stream() + .map(CoolestString::new) + .collect(Collectors.toList()) + ); + + Channel channel1 = Channel.create(); + Channel channel2 = Channel.create(); + Channel channel3 = Channel.create(); + Channel channel4 = Channel.create(); + Channel channel5 = Channel.create(); + Channel channel6 = Channel.create(); + + Messaging messaging = Messaging.builder() + .publisher(channel1, ReactiveStreams.of(testData.expected.get(0)).map(Message::of)) + .processor(channel1, channel2, ReactiveStreams.>builder() + .map(o -> Message.of((CoolerString) o.getPayload()))) + .processor(channel2, channel3, ReactiveStreams.>builder() + .map(Function.identity())) + .processor(channel3, channel4, ReactiveStreams.>builder() + .map(s -> Message.of((CoolestString) s.getPayload()))) + .processor(channel4, channel5, ReactiveStreams.>builder() + .map(s -> Message.of((CoolestString) s.getPayload()))) + .processor(channel5, channel6, s -> s) + .listener(channel6, coolString -> testData.add((CoolestString) coolString)) + .build() + .start(); + + testData.assertEqualsAnyOrder(); + + messaging.stop(); + } + + @Test + void processor() { + List testValues = List.of("test1"); + + LatchedTestData testData = new LatchedTestData<>( + testValues.stream() + .map(CoolestString::new) + .collect(Collectors.toList()) + ); + + Channel channel1 = Channel.create(); + Channel channel2 = Channel.create(); + Channel channel3 = Channel.create(); + Channel channel4 = Channel.create(); + Channel channel5 = Channel.create(); + Channel channel6 = Channel.create(); + + Messaging messaging = Messaging.builder() + .publisher(channel1, ReactiveStreams.of(testData.expected.get(0)).map(Message::of)) + .processor(channel1, channel2, ReactiveStreams.>builder() + .map(o -> Message.of((CoolerString) o.getPayload())).buildRs()) + .processor(channel2, channel3, ReactiveStreams.>builder() + .map(Function.identity()).buildRs()) + .processor(channel3, channel4, ReactiveStreams.>builder() + .map(s -> Message.of((CoolestString) s.getPayload())).buildRs()) + .processor(channel4, channel5, ReactiveStreams.>builder() + .map(s -> Message.of((CoolestString) s.getPayload())).buildRs()) + .processor(channel5, channel6, s -> s) + .listener(channel6, coolString -> testData.add((CoolestString) coolString)) + .build() + .start(); + + testData.assertEqualsAnyOrder(); + + messaging.stop(); + } + + @Test + @SuppressWarnings("unchecked") + void multiSubscriber() { + List testValues = List.of("test1"); + + LatchedTestData testData = new LatchedTestData<>( + testValues.stream() + .map(CoolerString::new) + .collect(Collectors.toList()) + ); + + Channel channel1 = Channel.create(); + + Messaging messaging = Messaging.builder() + .publisher(channel1, ReactiveStreams.of(testData.expected.get(0)).map(Message::of)) + .subscriber(channel1, multi -> multi + .map((Object m) -> (CoolerString) ((Message) m).getPayload()) + .forEach((CoolString m) -> testData.add((CoolerString) m)) + ) + .build() + .start(); + + testData.assertEqualsAnyOrder(); + + messaging.stop(); + } + + @Test + void subscriberBuilder() { + List testValues = List.of("test1"); + + LatchedTestData testData = new LatchedTestData<>( + testValues.stream() + .map(CoolerString::new) + .collect(Collectors.toList()) + ); + + Channel channel1 = Channel.create(); + + Messaging messaging = Messaging.builder() + .publisher(channel1, ReactiveStreams.of(testData.expected.get(0)).map(Message::of)) + .subscriber(channel1, ReactiveStreams.>builder() + .map(Message::getPayload) + .forEach(testData::add)) + .build() + .start(); + + testData.assertEqualsAnyOrder(); + + messaging.stop(); + } + + @Test + void subscriber() { + List testValues = List.of("test1"); + + LatchedTestData testData = new LatchedTestData<>( + testValues.stream() + .map(CoolerString::new) + .collect(Collectors.toList()) + ); + + Channel channel1 = Channel.create(); + + Messaging messaging = Messaging.builder() + .publisher(channel1, ReactiveStreams.of(testData.expected.get(0)).map(Message::of)) + .subscriber(channel1, ReactiveStreams.>builder() + .map(Message::getPayload) + .forEach(testData::add) + .build()) + .build() + .start(); + + testData.assertEqualsAnyOrder(); + + messaging.stop(); + } + + static class CoolestString extends CoolerString { + public CoolestString(final String content) { + super(content); + } + } + + static class CoolerString extends CoolString { + public CoolerString(final String content) { + super(content); + } + } + + static class CoolString implements CharSequence { + + private final String content; + + public CoolString(String content) { + this.content = content; + } + + @Override + public int length() { + return content.length(); + } + + @Override + public char charAt(final int index) { + return content.charAt(index); + } + + @Override + public CharSequence subSequence(final int start, final int end) { + return content.subSequence(start, end); + } + + @Override + public String toString() { + return content; + } + } +} diff --git a/messaging/messaging/src/test/java/io/helidon/messaging/EmitterTest.java b/messaging/messaging/src/test/java/io/helidon/messaging/EmitterTest.java new file mode 100644 index 00000000000..44afd3ed830 --- /dev/null +++ b/messaging/messaging/src/test/java/io/helidon/messaging/EmitterTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import io.helidon.common.context.Context; +import io.helidon.common.context.Contexts; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.everyItem; +import static org.hamcrest.Matchers.startsWith; + +import org.eclipse.microprofile.reactive.messaging.Message; +import org.junit.jupiter.api.Test; + +public class EmitterTest { + + + @Test + void emit() throws InterruptedException { + LatchedTestData testData = new LatchedTestData<>(List.of("test1", "test2", "test3", "test4")); + + Channel simpleChannel = Channel.create("simple-channel"); + + Emitter emitter = Emitter.create(simpleChannel); + + Messaging messaging = Messaging.builder() + .emitter(emitter) + .listener(simpleChannel, testData::add) + .build(); + + messaging.start(); + + emitter.send(Message.of("test1")); + emitter.send("test2"); + emitter.send(Message.of("test3")); + emitter.send("test4"); + + testData.await(200, TimeUnit.MILLISECONDS); + messaging.stop(); + + testData.assertEquals(); + } + + + @Test + void emitWithContext() throws InterruptedException { + LatchedTestData testData = new LatchedTestData<>(List.of("test1", "test2", "test3", "test4")); + + Channel channel1 = Channel.create(); + Channel channel2 = Channel.create(); + + Emitter emitter = Emitter.create(channel1); + + LinkedList contextIdList = new LinkedList<>(); + + Messaging messaging = Messaging.builder() + .emitter(emitter) + .processor(channel1, channel2, s -> { + Contexts.context() + .map(Context::id) + .ifPresent(contextIdList::add); + return s; + }) + .listener(channel2, testData::add) + .build(); + + messaging.start(); + + emitter.send(Message.of("test1")); + emitter.send("test2"); + emitter.send(Message.of("test3")); + emitter.send("test4"); + + testData.await(200, TimeUnit.MILLISECONDS); + messaging.stop(); + + String[] expected = contextIdList.stream().distinct().toArray(String[]::new); + assertThat("Contexts should be unique for every message", contextIdList, contains(expected)); + assertThat(contextIdList, everyItem(startsWith(Emitter.EMITTER_CONTEXT_PREFIX))); + + testData.assertEquals(); + } + + + @Test + void broadcast() throws InterruptedException { + final List TEST_DATA = List.of("test1", "test2", "test3", "test4"); + + LatchedTestData testData1 = new LatchedTestData<>(TEST_DATA); + LatchedTestData testData2 = new LatchedTestData<>(TEST_DATA); + LatchedTestData testData3 = new LatchedTestData<>(TEST_DATA); + + Channel simpleChannel1 = Channel.create("simple-channel-1"); + Channel simpleChannel2 = Channel.create("simple-channel-2"); + Channel simpleChannel3 = Channel.create("simple-channel-3"); + + + Emitter emitter = Emitter.builder() + .channel(simpleChannel1) + .channel(simpleChannel2) + .channel(simpleChannel3) + .build(); + + Messaging messaging = Messaging.builder() + // register one emitter for 3 channels + .emitter(emitter) + .listener(simpleChannel1, testData1::add) + .listener(simpleChannel2, testData2::add) + .listener(simpleChannel3, testData3::add) + .build(); + + messaging.start(); + + emitter.send(Message.of("test1")); + emitter.send("test2"); + emitter.send(Message.of("test3")); + emitter.send("test4"); + + testData3.await(200, TimeUnit.MILLISECONDS); + messaging.stop(); + + testData1.assertEquals(); + testData2.assertEquals(); + testData3.assertEquals(); + } +} diff --git a/messaging/messaging/src/test/java/io/helidon/messaging/LatchedTestData.java b/messaging/messaging/src/test/java/io/helidon/messaging/LatchedTestData.java new file mode 100644 index 00000000000..6a5903fdb91 --- /dev/null +++ b/messaging/messaging/src/test/java/io/helidon/messaging/LatchedTestData.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.fail; + +public class LatchedTestData extends CountDownLatch { + + final List expected; + final ArrayList result = new ArrayList<>(); + + public LatchedTestData(List expected) { + super(expected.size()); + this.expected = expected; + } + + void add(E value) { + result.add(value); + countDown(); + } + + void assertEquals(List expected) { + try { + this.await(200, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + fail(e); + } + assertThat(result, equalTo(expected)); + } + + void assertEquals() { + assertEquals(this.expected); + } + + void assertEqualsAnyOrder() { + try { + this.await(200, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + fail(e); + } + assertThat(result, containsInAnyOrder(expected.toArray())); + } +} \ No newline at end of file diff --git a/messaging/messaging/src/test/java/io/helidon/messaging/MessagingTest.java b/messaging/messaging/src/test/java/io/helidon/messaging/MessagingTest.java new file mode 100644 index 00000000000..25193a04933 --- /dev/null +++ b/messaging/messaging/src/test/java/io/helidon/messaging/MessagingTest.java @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; + +import io.helidon.common.reactive.Multi; +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +public class MessagingTest { + + @AfterEach + void cleanUp() { + TestConnector.reset(); + } + + + @Test + void pubBuilderToListenerConsumer() { + LatchedTestData testData = new LatchedTestData<>(List.of("test1", "test2", "test3")); + + Channel channel1 = Channel.create("channel1"); + + Messaging.builder() + .publisher(channel1, ReactiveStreams.fromIterable(testData.expected).map(Message::of)) + .listener(channel1, testData::add) + .build() + .start(); + + testData.assertEquals(); + } + + @Test + void pubBuilderToSubBuilder() { + LatchedTestData testData = new LatchedTestData<>(List.of("test1", "test2", "test3")); + + Channel channel1 = Channel.create("channel1"); + + Messaging.builder() + .publisher(channel1, ReactiveStreams.fromIterable(testData.expected).map(Message::of)) + .subscriber(channel1, ReactiveStreams.>builder() + .map(Message::getPayload) + .forEach(testData::add)) + .build() + .start(); + + testData.assertEquals(); + } + + @Test + void pubToSubAck() { + LatchedTestData testData = new LatchedTestData<>(List.of("test1", "test2", "test3")); + + Channel channel1 = Channel.create("channel1"); + + Messaging.builder() + .publisher(channel1, ReactiveStreams.fromIterable(testData.expected).buildRs(), Message::of) + .subscriber(channel1, ReactiveStreams.>builder() + .map(Message::getPayload) + .forEach(testData::add) + .build()) + .build() + .start(); + + testData.assertEquals(); + } + + @Test + void multiToListener() { + LatchedTestData testData = new LatchedTestData<>(List.of("test1", "test2", "test3")); + + Channel channel1 = Channel.create("channel1"); + + Messaging.builder() + .publisher(channel1, Multi.create(testData.expected), Message::of) + .listener(channel1, testData::add) + .build() + .start(); + + testData.assertEquals(); + } + + @Test + void simpleChannelRS() { + LatchedTestData testData = new LatchedTestData<>(List.of("test1", "test2", "test3")); + + Channel channel1 = Channel.create("channel1"); + + Messaging.builder() + .publisher(channel1, ReactiveStreams.fromIterable(testData.expected).map(Message::of)) + .subscriber(channel1, ReactiveStreams.>builder() + .peek(Message::ack) + .map(Message::getPayload) + .forEach(testData::add)) + .build() + .start(); + + testData.assertEquals(); + } + + @Test + void simpleChannelWithMulti() { + LatchedTestData testData = new LatchedTestData<>(List.of("test1", "test2", "test3")); + + Channel channel1 = Channel.create("channel1"); + + Messaging.builder() + .publisher(channel1, Multi.create(testData.expected).map(Message::of)) + .listener(channel1, testData::add) + .build() + .start(); + + testData.assertEquals(); + } + + @Test + void pubConsumer() { + LatchedTestData testData = new LatchedTestData<>(List.of("test1", "test2", "test3")); + TestMessages testMessages = new TestMessages<>(); + + Channel channel1 = Channel.create("channel1"); + + Messaging.builder() + .publisher(channel1, ReactiveStreams.fromIterable(testData.expected).map(testMessages::of)) + .listener(channel1, testData::add) + .build() + .start(); + + testData.assertEquals(); + testMessages.assertAllAcked(); + } + + @Test + void pubConsumerMsg() { + LatchedTestData testData = new LatchedTestData<>(List.of("test1", "test2", "test3")); + TestMessages testMessages = new TestMessages<>(); + + Channel channel1 = Channel.create("channel1"); + + Messaging.builder() + .publisher(channel1, Multi.create(testData.expected).map(testMessages::of)) + .subscriber(channel1, multi -> multi.map(Message::getPayload).forEach(testData::add)) + .build() + .start(); + + testData.assertEquals(); + testMessages.assertNoneAcked(); + } + + @Test + void outgoingGenerate() { + LatchedTestData testData = new LatchedTestData<>(List.of("Infinite Hello!!", "Infinite Hello!!", "Infinite Hello!!")); + + Channel channel1 = Channel.create("channel1"); + + Messaging.builder() + .publisher(channel1, ReactiveStreams.generate(() -> Message.of("Infinite Hello!!"))) + .subscriber(channel1, ReactiveStreams.>builder() + .limit(3) + .map(Message::getPayload) + .forEach(testData::add) + ) + .build() + .start(); + + testData.assertEquals(); + } + + @Test + void simpleChannelWithProcessorRS() { + LatchedTestData testData = new LatchedTestData<>(List.of("test1", "test2", "test3")); + + Channel simpleChannel = Channel.create("simple-channel"); + Channel middleChannel = Channel.create("middle-channel"); + + Messaging.builder() + .publisher(middleChannel, ReactiveStreams.fromIterable(testData.expected).map(Message::of)) + .processor(middleChannel, simpleChannel, ReactiveStreams.>builder() + .map(Message::getPayload) + .map(s -> ">>" + s) + .map(Message::of) + ) + .subscriber(simpleChannel, ReactiveStreams.>builder() + .peek(Message::ack) + .map(Message::getPayload) + .forEach(testData::add)) + .build() + .start(); + + testData.assertEquals(testData.expected.stream().map(s -> ">>" + s).collect(Collectors.toList())); + } + + @Test + void incomingFromConnector() { + LatchedTestData testData = + new LatchedTestData<>(TestConnector.TEST_DATA.stream() + .map(s -> (CharSequence) s) + .collect(Collectors.toList())); + + Channel channel = Channel.create("from-test-connector"); + + HashMap p = new HashMap<>(); + p.put("mp.messaging.outgoing." + channel.name() + ".connector", TestConnector.CONNECTOR_NAME); + Config config = Config.builder() + .sources(ConfigSources.create(p)) + .build(); + + Messaging.builder() + .config(config) + .connector(TestConnector.create()) + .subscriber(channel, ReactiveStreams.>builder() + .peek(Message::ack) + .map(Message::getPayload) + .forEach(testData::add)) + .build() + .start(); + + testData.assertEquals(); + } + + @Test + void incomingFromStoppableConnector() { + LatchedTestData testData = new LatchedTestData<>(TestConnector.TEST_DATA); + + Channel channel = Channel.create("from-test-connector"); + + HashMap p = new HashMap<>(); + p.put("mp.messaging.outgoing." + channel.name() + ".connector", TestConnector.CONNECTOR_NAME); + Config config = Config.builder() + .sources(ConfigSources.create(p)) + .build(); + + + TestConnector connector = TestConnector.create(); + + Messaging messaging = Messaging.builder() + .config(config) + .connector(connector) + .listener(channel, testData::add) + .build(); + + messaging.start(); + testData.assertEquals(); + + assertThat(connector.stoppedFuture.isDone(), is(false)); + messaging.stop(); + assertThat(connector.stoppedFuture.isDone(), is(true)); + } + + @Test + void outgoingToConnector() throws InterruptedException { + Channel channel = Channel.create("to-test-connector"); + + HashMap p = new HashMap<>(); + p.put("mp.messaging.incoming." + channel.name() + ".connector", TestConnector.CONNECTOR_NAME); + Config config = Config.builder() + .sources(ConfigSources.create(p)) + .disableSystemPropertiesSource() + .disableEnvironmentVariablesSource() + .build(); + + Messaging.builder() + .config(config) + .connector(TestConnector.create()) + .publisher(channel, ReactiveStreams.fromIterable(TestConnector.TEST_DATA).map(Message::of)) + .build() + .start(); + + TestConnector.latch.await(200, TimeUnit.MILLISECONDS); + } + + @Test + void processorFunctionBetweenConnectors() throws InterruptedException { + + Channel fromConnectorChannel = Channel.create("from-test-connector"); + Channel toConnectorChannel = Channel.create("to-test-connector"); + + HashMap p = new HashMap<>(); + p.put("mp.messaging.outgoing." + fromConnectorChannel.name() + ".connector", TestConnector.CONNECTOR_NAME); + p.put("mp.messaging.incoming." + toConnectorChannel.name() + ".connector", TestConnector.CONNECTOR_NAME); + Config config = Config.builder() + .sources(ConfigSources.create(p)) + .build(); + + Messaging.builder() + .config(config) + .connector(TestConnector.create()) + .processor(fromConnectorChannel, toConnectorChannel, payload -> ">>" + payload) + .build() + .start(); + + TestConnector.latch.await(200, TimeUnit.MILLISECONDS); + assertThat(TestConnector.receivedData, equalTo(TestConnector.TEST_DATA.stream().map(s -> ">>" + s).collect(Collectors.toList()))); + } + + + @Test + void processorBetweenConnectors() throws InterruptedException { + + Channel fromConnectorChannel = Channel.create("from-test-connector"); + Channel toConnectorChannel = Channel.create("to-test-connector"); + + HashMap p = new HashMap<>(); + p.put("mp.messaging.outgoing." + fromConnectorChannel.name() + ".connector", TestConnector.CONNECTOR_NAME); + p.put("mp.messaging.incoming." + toConnectorChannel.name() + ".connector", TestConnector.CONNECTOR_NAME); + Config config = Config.builder() + .sources(ConfigSources.create(p)) + .build(); + + Messaging.builder() + .config(config) + .connector(TestConnector.create()) + .processor(fromConnectorChannel, toConnectorChannel, ReactiveStreams.>builder() + .peek(Message::ack) + .map(Message::getPayload) + .map(s -> ">>" + s) + .map(Message::of) + ) + .build() + .start(); + + TestConnector.latch.await(200, TimeUnit.MILLISECONDS); + assertThat(TestConnector.receivedData, equalTo(TestConnector.TEST_DATA.stream().map(s -> ">>" + s).collect(Collectors.toList()))); + } + + @Test + void processorConfigApi() throws InterruptedException, TimeoutException, ExecutionException { + + CompletableFuture> completableFuture = new CompletableFuture<>(); + HashSet publisherProps = new HashSet<>(); + + Channel fromConnectorChannel = Channel.builder() + .name("from-test-connector") + .publisherConfig(TestConfigurableConnector.configBuilder() + .url("http://source.com") + .port(8888) + .build() + ) + .build(); + + Channel>> toConnectorChannel = Channel.>>builder() + .name("to-test-connector") + .subscriberConfig(TestConfigurableConnector.configBuilder() + .url("http://sink.com") + .port(9999) + .build() + ) + .build(); + + Messaging.builder() + .connector(TestConfigurableConnector.create()) + + .processor(fromConnectorChannel, toConnectorChannel, + ReactiveStreams.>builder() + .map(Message::getPayload) + .peek(publisherProps::add) + .map(s -> Message.of(completableFuture)) + ) + + .build() + .start(); + + Map subscriberProps = completableFuture.get(200, TimeUnit.MILLISECONDS); + + assertThat(subscriberProps, equalTo(Map.of( + "channel-name", "to-test-connector", + "connector", "test-configurable-connector", + "port", "9999", + "url", "http://sink.com" + ))); + + assertThat(publisherProps, equalTo(Set.of( + "channel-name=from-test-connector", + "connector=test-configurable-connector", + "port=8888", + "url=http://source.com" + ))); + } +} diff --git a/messaging/messaging/src/test/java/io/helidon/messaging/StateTest.java b/messaging/messaging/src/test/java/io/helidon/messaging/StateTest.java new file mode 100644 index 00000000000..043b7376e44 --- /dev/null +++ b/messaging/messaging/src/test/java/io/helidon/messaging/StateTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class StateTest { + + @Test + void happyPath() { + Messaging messaging = Messaging.builder().build(); + messaging.start(); + messaging.stop(); + } + + @Test + void stopTwice() { + Messaging messaging = Messaging.builder().build(); + messaging.start(); + messaging.stop(); + assertThrows(MessagingException.class, messaging::stop); + } + + @Test + void startTwice() { + Messaging messaging = Messaging.builder().build(); + messaging.start(); + assertThrows(MessagingException.class, messaging::start); + } + + @Test + void startStopStart() { + Messaging messaging = Messaging.builder().build(); + messaging.start(); + messaging.stop(); + assertThrows(MessagingException.class, messaging::start); + } + + @Test + void stopBeforeStart() { + Messaging messaging = Messaging.builder().build(); + assertThrows(MessagingException.class, messaging::stop); + } +} diff --git a/messaging/messaging/src/test/java/io/helidon/messaging/TestConfigurableConnector.java b/messaging/messaging/src/test/java/io/helidon/messaging/TestConfigurableConnector.java new file mode 100644 index 00000000000..737a470e8ea --- /dev/null +++ b/messaging/messaging/src/test/java/io/helidon/messaging/TestConfigurableConnector.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import io.helidon.config.mp.MpConfig; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.spi.Connector; +import org.eclipse.microprofile.reactive.messaging.spi.ConnectorFactory; +import org.eclipse.microprofile.reactive.messaging.spi.IncomingConnectorFactory; +import org.eclipse.microprofile.reactive.messaging.spi.OutgoingConnectorFactory; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; + +@Connector(TestConfigurableConnector.CONNECTOR_NAME) +public class TestConfigurableConnector implements IncomingConnectorFactory, OutgoingConnectorFactory { + + public static final String CONNECTOR_NAME = "test-configurable-connector"; + + private TestConfigurableConnector() { + } + + public static TestConfigurableConnector create() { + return new TestConfigurableConnector(); + } + + @Override + public PublisherBuilder> getPublisherBuilder(final Config config) { + io.helidon.config.Config helidonConfig = MpConfig.toHelidonConfig(config); + printConfig(helidonConfig); + return ReactiveStreams.fromIterable(config.getPropertyNames()) + .map(n -> n + "=" + config.getValue(n, String.class)) + .map(Message::of); + } + + @Override + public SubscriberBuilder, Void> getSubscriberBuilder(final Config config) { + io.helidon.config.Config helidonConfig = MpConfig.toHelidonConfig(config); + printConfig(helidonConfig); + return ReactiveStreams.>>>builder() + .map(Message::getPayload) + .forEach(f -> f.complete(helidonConfig + .traverse() + .map(c -> Map.entry(c.key().name(), c.asString().get())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))) + ); + } + + private static void printConfig(io.helidon.config.Config c) { + c.asMap().orElse(Map.of()).forEach((key, value) -> System.out.println(key + ": " + value)); + } + + public static ConfigBuilder configBuilder() { + return new ConfigBuilder(); + } + + public static class ConfigBuilder extends ConnectorConfigBuilder { + + protected ConfigBuilder() { + super(); + super.property(ConnectorFactory.CONNECTOR_ATTRIBUTE, CONNECTOR_NAME); + } + + public ConfigBuilder url(String url) { + super.property("url", url); + return this; + } + + public ConfigBuilder port(int port) { + super.property("port", String.valueOf(port)); + return this; + } + } +} diff --git a/messaging/messaging/src/test/java/io/helidon/messaging/TestConnector.java b/messaging/messaging/src/test/java/io/helidon/messaging/TestConnector.java new file mode 100644 index 00000000000..565a5d8aeb3 --- /dev/null +++ b/messaging/messaging/src/test/java/io/helidon/messaging/TestConnector.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.spi.Connector; +import org.eclipse.microprofile.reactive.messaging.spi.IncomingConnectorFactory; +import org.eclipse.microprofile.reactive.messaging.spi.OutgoingConnectorFactory; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; + +@Connector(TestConnector.CONNECTOR_NAME) +public class TestConnector implements IncomingConnectorFactory, OutgoingConnectorFactory, Stoppable { + + public static final String CONNECTOR_NAME = "test-connector"; + public static final String TEST_PAYLOAD = "test-payload"; + public static final int TEST_STREAM_SIZE = 5; + public static final List TEST_DATA = List.of(TEST_PAYLOAD, TEST_PAYLOAD, TEST_PAYLOAD, TEST_PAYLOAD, TEST_PAYLOAD); + public static CountDownLatch latch = new CountDownLatch(TEST_DATA.size()); + public CompletableFuture stoppedFuture = new CompletableFuture<>(); + public static List receivedData = new ArrayList<>(); + + private TestConnector() { + + } + + public static TestConnector create() { + return new TestConnector(); + } + + @Override + public PublisherBuilder> getPublisherBuilder(final Config config) { + Optional customPayload = config.getOptionalValue(TEST_PAYLOAD, String.class); + if (customPayload.isPresent()) { + return ReactiveStreams.generate(customPayload::get).limit(TEST_STREAM_SIZE).map(Message::of); + } + return ReactiveStreams.fromIterable(TEST_DATA).map(Message::of); + } + + @Override + public SubscriberBuilder, Void> getSubscriberBuilder(final Config config) { + return ReactiveStreams.>builder() + .map(Message::getPayload) + .forEach(o -> { + receivedData.add(o); + latch.countDown(); + }); + } + + static void reset() { + TestConnector.receivedData.clear(); + TestConnector.latch = new CountDownLatch(TestConnector.TEST_DATA.size()); + } + + @Override + public void stop() { + stoppedFuture.complete(null); + } + + public static ConfigBuilder configBuilder() { + return new ConfigBuilder(); + } + + public static class ConfigBuilder extends ConnectorConfigBuilder { + public ConfigBuilder url(String url) { + super.property("url", url); + return this; + } + + public ConfigBuilder port(int port) { + super.property("port", String.valueOf(port)); + return this; + } + } +} diff --git a/messaging/messaging/src/test/java/io/helidon/messaging/TestMessages.java b/messaging/messaging/src/test/java/io/helidon/messaging/TestMessages.java new file mode 100644 index 00000000000..d7221041172 --- /dev/null +++ b/messaging/messaging/src/test/java/io/helidon/messaging/TestMessages.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging; + +import java.util.LinkedList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import org.eclipse.microprofile.reactive.messaging.Message; + +public class TestMessages extends LinkedList> { + + public void assertAllAcked() { + assertThat(this.stream().allMatch(TestMessage::acked), is(true)); + } + + public void assertNoneAcked() { + assertThat(this.stream().anyMatch(TestMessage::acked), is(false)); + } + + public Message of(T payload) { + TestMessage m = new TestMessage(payload); + this.add(m); + return m; + } + + static class TestMessage implements Message { + + boolean acked = false; + T payload; + + TestMessage(final T payload) { + this.payload = payload; + } + + boolean acked() { + return acked; + } + + @Override + public T getPayload() { + return payload; + } + + @Override + public CompletionStage ack() { + acked = true; + return CompletableFuture.completedStage(null); + } + + } +} diff --git a/messaging/pom.xml b/messaging/pom.xml index 3fd2ec73ad1..25b1b682d78 100644 --- a/messaging/pom.xml +++ b/messaging/pom.xml @@ -26,12 +26,14 @@ helidon-project 2.0.0-SNAPSHOT - pom io.helidon.messaging helidon-messaging-project - Helidon Messaging + Helidon Messaging Project + + pom - connectors + messaging + kafka diff --git a/metrics/metrics/pom.xml b/metrics/metrics/pom.xml index 42e0809d7f5..f29a18a64ed 100644 --- a/metrics/metrics/pom.xml +++ b/metrics/metrics/pom.xml @@ -42,10 +42,6 @@ io.helidon.common helidon-common - - io.helidon.common - helidon-common-metrics - io.helidon.config helidon-config-mp @@ -63,8 +59,8 @@ helidon-webserver-cors - io.helidon.media.jsonp - helidon-media-jsonp-server + io.helidon.media + helidon-media-jsonp io.helidon.config diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/BaseRegistry.java b/metrics/metrics/src/main/java/io/helidon/metrics/BaseRegistry.java index 7de2c1c9623..bb5fa8a837f 100644 --- a/metrics/metrics/src/main/java/io/helidon/metrics/BaseRegistry.java +++ b/metrics/metrics/src/main/java/io/helidon/metrics/BaseRegistry.java @@ -56,27 +56,31 @@ final class BaseRegistry extends Registry { private static final String CONFIG_METRIC_ENABLED_BASE = "base."; static final String BASE_ENABLED_KEY = CONFIG_METRIC_ENABLED_BASE + "enabled"; - private static final Metadata MEMORY_USED_HEAP = - new HelidonMetadata("memory.usedHeap", - "Used Heap Memory", - "Displays the amount of used heap memory in bytes.", - MetricType.GAUGE, - MetricUnits.BYTES); - - private static final Metadata MEMORY_COMMITTED_HEAP = - new HelidonMetadata("memory.committedHeap", - "Committed Heap Memory", + private static final Metadata MEMORY_USED_HEAP = Metadata.builder() + .withName("memory.usedHeap") + .withDisplayName("Used Heap Memory") + .withDescription("Displays the amount of used heap memory in bytes.") + .withType(MetricType.GAUGE) + .withUnit(MetricUnits.BYTES) + .build(); + + private static final Metadata MEMORY_COMMITTED_HEAP = Metadata.builder() + .withName("memory.committedHeap") + .withDisplayName("Committed Heap Memory") + .withDescription( "Displays the amount of memory in bytes that is " + "committed for the Java virtual " + "machine to use. This amount of memory is " + "guaranteed for the Java virtual " - + "machine to use.", - MetricType.GAUGE, - MetricUnits.BYTES); - - private static final Metadata MEMORY_MAX_HEAP = - new HelidonMetadata("memory.maxHeap", - "Max Heap Memory", + + "machine to use.") + .withType(MetricType.GAUGE) + .withUnit(MetricUnits.BYTES) + .build(); + + private static final Metadata MEMORY_MAX_HEAP = Metadata.builder() + .withName("memory.maxHeap") + .withDisplayName("Max Heap Memory") + .withDescription( "Displays the maximum amount of heap memory in bytes that can" + " be used for " + "memory management. This attribute displays -1 if " @@ -88,84 +92,94 @@ final class BaseRegistry extends Registry { + "committed memory. The Java virtual machine may fail" + " to allocate memory " + "even if the amount of used memory does not exceed " - + "this maximum size.", - MetricType.GAUGE, - MetricUnits.BYTES); - - private static final Metadata JVM_UPTIME = - new HelidonMetadata("jvm.uptime", - "JVM Uptime", + + "this maximum size.") + .withType(MetricType.GAUGE) + .withUnit(MetricUnits.BYTES) + .build(); + + private static final Metadata JVM_UPTIME = Metadata.builder() + .withName("jvm.uptime") + .withDisplayName("JVM Uptime") + .withDescription( "Displays the start time of the Java virtual machine in " + "milliseconds. This " + "attribute displays the approximate time when the Java " + "virtual machine " - + "started.", - MetricType.GAUGE, - MetricUnits.MILLISECONDS); - - private static final Metadata THREAD_COUNT = - new HelidonMetadata("thread.count", - "Thread Count", - "Displays the current number of live threads including both " - + "daemon and nondaemon threads", - MetricType.GAUGE, - MetricUnits.NONE); - - private static final Metadata THREAD_DAEMON_COUNT = - new HelidonMetadata("thread.daemon.count", - "Daemon Thread Count", - "Displays the current number of live daemon threads.", - MetricType.GAUGE, - MetricUnits.NONE); - - private static final Metadata THREAD_MAX_COUNT = - new HelidonMetadata("thread.max.count", - "Peak Thread Count", - "Displays the peak live thread count since the Java " + + "started.") + .withType(MetricType.GAUGE) + .withUnit(MetricUnits.MILLISECONDS) + .build(); + + private static final Metadata THREAD_COUNT = Metadata.builder() + .withName("thread.count") + .withDisplayName("Thread Count") + .withDescription("Displays the current number of live threads including both " + + "daemon and nondaemon threads") + .withType(MetricType.GAUGE) + .withUnit(MetricUnits.NONE) + .build(); + + private static final Metadata THREAD_DAEMON_COUNT = Metadata.builder() + .withName("thread.daemon.count") + .withDisplayName("Daemon Thread Count") + .withDescription("Displays the current number of live daemon threads.") + .withType(MetricType.GAUGE) + .withUnit(MetricUnits.NONE) + .build(); + + private static final Metadata THREAD_MAX_COUNT = Metadata.builder() + .withName("thread.max.count") + .withDisplayName("Peak Thread Count") + .withDescription("Displays the peak live thread count since the Java " + "virtual machine started or " + "peak was reset. This includes daemon and " - + "non-daemon threads.", - MetricType.GAUGE, - MetricUnits.NONE); - - private static final Metadata CL_LOADED_COUNT = - new HelidonMetadata("classloader.loadedClasses.count", - "Current Loaded Class Count", - "Displays the number of classes that are currently loaded in " - + "the Java virtual machine.", - MetricType.GAUGE, - MetricUnits.NONE); - - private static final Metadata CL_LOADED_TOTAL = - new HelidonMetadata("classloader.loadedClasses.total", - "Total Loaded Class Count", - "Displays the total number of classes that have been loaded " - + "since the Java virtual machine has started execution.", - MetricType.COUNTER, - MetricUnits.NONE); - - private static final Metadata CL_UNLOADED_COUNT = - new HelidonMetadata("classloader.unloadedClasses.total", - "Total Unloaded Class Count", - "Displays the total number of classes unloaded since the Java " - + "virtual machine has started execution.", - MetricType.COUNTER, - MetricUnits.NONE); - - private static final Metadata OS_AVAILABLE_CPU = - new HelidonMetadata("cpu.availableProcessors", - "Available Processors", - "Displays the number of processors available to the Java " + + "non-daemon threads.") + .withType(MetricType.GAUGE) + .withUnit(MetricUnits.NONE) + .build(); + + private static final Metadata CL_LOADED_COUNT = Metadata.builder() + .withName("classloader.loadedClasses.count") + .withDisplayName("Current Loaded Class Count") + .withDescription("Displays the number of classes that are currently loaded in " + + "the Java virtual machine.") + .withType(MetricType.GAUGE) + .withUnit(MetricUnits.NONE) + .build(); + + private static final Metadata CL_LOADED_TOTAL = Metadata.builder() + .withName("classloader.loadedClasses.total") + .withDisplayName("Total Loaded Class Count") + .withDescription("Displays the total number of classes that have been loaded " + + "since the Java virtual machine has started execution.") + .withType(MetricType.COUNTER) + .withUnit(MetricUnits.NONE) + .build(); + + private static final Metadata CL_UNLOADED_COUNT = Metadata.builder() + .withName("classloader.unloadedClasses.total") + .withDisplayName("Total Unloaded Class Count") + .withDescription("Displays the total number of classes unloaded since the Java " + + "virtual machine has started execution.") + .withType(MetricType.COUNTER) + .withUnit(MetricUnits.NONE) + .build(); + + private static final Metadata OS_AVAILABLE_CPU = Metadata.builder() + .withName("cpu.availableProcessors") + .withDisplayName("Available Processors") + .withDescription("Displays the number of processors available to the Java " + "virtual machine. This " + "value may change during a particular invocation of" - + " the virtual machine.", - MetricType.GAUGE, - MetricUnits.NONE); - - private static final Metadata OS_LOAD_AVERAGE = - new HelidonMetadata("cpu.systemLoadAverage", - "System Load Average", - "Displays the system load average for the last minute. The " + + " the virtual machine.") + .withType(MetricType.GAUGE) + .withUnit(MetricUnits.NONE) + .build(); + + private static final Metadata OS_LOAD_AVERAGE = Metadata.builder() + .withName("cpu.systemLoadAverage") + .withDisplayName("System Load Average") + .withDescription("Displays the system load average for the last minute. The " + "system load average " + "is the sum of the number of runnable entities " + "queued to the available " @@ -182,9 +196,10 @@ final class BaseRegistry extends Registry { + "and may be queried frequently. The load average may" + " be unavailable on some " + "platforms where it is expensive to implement this " - + "method.", - MetricType.GAUGE, - MetricUnits.NONE); + + "method.") + .withType(MetricType.GAUGE) + .withUnit(MetricUnits.NONE) + .build(); private final Config config; @@ -237,25 +252,31 @@ public static Registry create(Config config) { } private static Metadata gcTimeMeta() { - return new HelidonMetadata("gc.time", - "Garbage Collection Time", + return Metadata.builder() + .withName("gc.time") + .withDisplayName("Garbage Collection Time") + .withDescription( "Displays the approximate accumulated collection elapsed time in milliseconds. " + "This attribute displays -1 if the collection elapsed time is undefined for this " + "collector. The Java virtual machine implementation may use a high resolution " + "timer to measure the elapsed time. This attribute may display the same value " + "even if the collection count has been incremented if the collection elapsed " - + "time is very short.", - MetricType.GAUGE, - MetricUnits.MILLISECONDS); + + "time is very short.") + .withType(MetricType.GAUGE) + .withUnit(MetricUnits.MILLISECONDS) + .build(); } private static Metadata gcCountMeta() { - return new HelidonMetadata("gc.total", - "Garbage Collection Count", + return Metadata.builder() + .withName("gc.total") + .withDisplayName("Garbage Collection Count") + .withDescription( "Displays the total number of collections that have occurred. This attribute lists " - + "-1 if the collection count is undefined for this collector.", - MetricType.COUNTER, - MetricUnits.NONE); + + "-1 if the collection count is undefined for this collector.") + .withType(MetricType.COUNTER) + .withUnit(MetricUnits.NONE) + .build(); } private static void register(BaseRegistry registry, Metadata meta, Metric metric, Tag... tags) { diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/HelidonMetadata.java b/metrics/metrics/src/main/java/io/helidon/metrics/HelidonMetadata.java deleted file mode 100644 index cf7fb65122c..00000000000 --- a/metrics/metrics/src/main/java/io/helidon/metrics/HelidonMetadata.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.metrics; - -import org.eclipse.microprofile.metrics.DefaultMetadata; -import org.eclipse.microprofile.metrics.MetricType; -import org.eclipse.microprofile.metrics.MetricUnits; - -/** - * Class HelidonMetadata. In MP Metrics 2.0, {@link org.eclipse.microprofile.metrics.Metadata} - * is now immutable and a builder was added. This class allows creation of metadata - * directly using a constructor to avoid switching to a builder in dozens of locations. - * Can be used from other packages unfortunately. - */ -public class HelidonMetadata extends DefaultMetadata { - - /** - * Construct immutable metadata. - * - * @param name Metric name. - * @param type Metric type. - */ - public HelidonMetadata(String name, MetricType type) { - super(name, null, null, type, MetricUnits.NONE, true); - } - - /** - * Construct immutable metadata. - * - * @param name Metric name. - * @param displayName Metric display name. - * @param description Metric description. - * @param type Metric type. - * @param unit Metric unit. - * @param reusable Reusable flag. - */ - public HelidonMetadata(String name, String displayName, String description, MetricType type, - String unit, boolean reusable) { - super(name, displayName, description, type, unit, reusable); - } - - /** - * Construct immutable metadata. - * - * @param name Metric name. - * @param displayName Metric display name. - * @param description Metric description. - * @param type Metric type. - * @param unit Metric unit. - */ - public HelidonMetadata(String name, String displayName, String description, MetricType type, String unit) { - super(name, displayName, description, type, unit, true); - } -} diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/HelidonTimer.java b/metrics/metrics/src/main/java/io/helidon/metrics/HelidonTimer.java index d339818efa3..9afe3f90eb9 100644 --- a/metrics/metrics/src/main/java/io/helidon/metrics/HelidonTimer.java +++ b/metrics/metrics/src/main/java/io/helidon/metrics/HelidonTimer.java @@ -280,8 +280,14 @@ private static class TimerImpl implements Timer { private final Clock clock; TimerImpl(String repoType, String name, Clock clock) { - this.meter = HelidonMeter.create(repoType, new HelidonMetadata(name, MetricType.METERED), clock); - this.histogram = HelidonHistogram.create(repoType, new HelidonMetadata(name, MetricType.HISTOGRAM)); + this.meter = HelidonMeter.create(repoType, Metadata.builder() + .withName(name) + .withType(MetricType.METERED) + .build(), clock); + this.histogram = HelidonHistogram.create(repoType, Metadata.builder() + .withName(name) + .withType(MetricType.HISTOGRAM) + .build()); this.clock = clock; } diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/InternalBridgeImpl.java b/metrics/metrics/src/main/java/io/helidon/metrics/InternalBridgeImpl.java deleted file mode 100644 index 09f01d7f6cb..00000000000 --- a/metrics/metrics/src/main/java/io/helidon/metrics/InternalBridgeImpl.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.metrics; - -import io.helidon.common.metrics.InternalBridge; -import io.helidon.common.metrics.InternalBridge.Metadata.MetadataBuilder; -import io.helidon.config.Config; - -/** - * Implements the metrics bridge interface based on MP Metrics 2.0. - */ -public class InternalBridgeImpl implements InternalBridge { - - @Override - public RegistryFactory getRegistryFactory() { - return io.helidon.metrics.RegistryFactory.getInstance(); - } - - @Override - public RegistryFactory createRegistryFactory() { - return io.helidon.metrics.RegistryFactory.create(); - } - - @Override - public RegistryFactory createRegistryFactory(Config config) { - return io.helidon.metrics.RegistryFactory.create(config); - } - - @Override - public MetricID.Factory getMetricIDFactory() { - return new InternalMetricIDImpl.FactoryImpl(); - } - - @Override - public MetadataBuilder.Factory getMetadataBuilderFactory() { - return new InternalMetadataBuilderImpl.FactoryImpl(); - } - -} diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/InternalMetadataBuilderImpl.java b/metrics/metrics/src/main/java/io/helidon/metrics/InternalMetadataBuilderImpl.java deleted file mode 100644 index f94727ef936..00000000000 --- a/metrics/metrics/src/main/java/io/helidon/metrics/InternalMetadataBuilderImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.metrics; - -import java.util.Map; - -import io.helidon.common.metrics.InternalBridge; -import io.helidon.common.metrics.InternalBridge.Metadata; -import io.helidon.common.metrics.InternalBridge.Metadata.MetadataBuilder; - -import org.eclipse.microprofile.metrics.MetricType; - -/** - * Fluent-style builder for version-neutral {@link Metadata}. - * - */ -class InternalMetadataBuilderImpl implements MetadataBuilder { - - private final org.eclipse.microprofile.metrics.MetadataBuilder delegate; - - InternalMetadataBuilderImpl() { - delegate = new org.eclipse.microprofile.metrics.MetadataBuilder(); - } - - @Override - public MetadataBuilder withName(String name) { - delegate.withName(name); - return this; - } - - @Override - public MetadataBuilder withDisplayName(String displayName) { - delegate.withDisplayName(displayName); - return this; - } - - @Override - public MetadataBuilder withDescription(String description) { - delegate.withDescription(description); - return this; - } - - @Override - public MetadataBuilder withType(MetricType type) { - delegate.withType(type); - return this; - } - - @Override - public MetadataBuilder withUnit(String unit) { - delegate.withUnit(unit); - return this; - } - - @Override - public MetadataBuilder reusable() { - delegate.reusable(); - return this; - } - - @Override - public MetadataBuilder notReusable() { - delegate.notReusable(); - return this; - } - - @Override - public Metadata.MetadataBuilder withTags(Map tags) { - return this; - } - - @Override - public InternalBridge.Metadata build() { - return new InternalMetadataImpl(delegate.build()); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return (obj != null) - && (this.getClass().isAssignableFrom(obj.getClass())) - && delegate.equals(((InternalMetadataBuilderImpl) obj).delegate); - } - - @Override - public String toString() { - return delegate.toString(); - } - - static class FactoryImpl implements Factory { - - @Override - public Metadata.MetadataBuilder newMetadataBuilder() { - return new InternalMetadataBuilderImpl(); - } - } -} diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/InternalMetadataImpl.java b/metrics/metrics/src/main/java/io/helidon/metrics/InternalMetadataImpl.java deleted file mode 100644 index 453fc4f041d..00000000000 --- a/metrics/metrics/src/main/java/io/helidon/metrics/InternalMetadataImpl.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.metrics; - -import java.util.Collections; -import java.util.Map; -import java.util.Optional; - -import io.helidon.common.metrics.InternalBridge.Metadata; - -import org.eclipse.microprofile.metrics.MetricType; - -/** - * Metrics 2.0-based implementation of the version-neutral {@code Metadata} interface. - */ -class InternalMetadataImpl implements Metadata { - - private final org.eclipse.microprofile.metrics.Metadata delegate; - - /** - * Creates a new metadata instance, delegating to the specified 2.0 metadata. - * @param delegate - */ - InternalMetadataImpl(org.eclipse.microprofile.metrics.Metadata delegate) { - this.delegate = delegate; - } - - @Override - public String getName() { - return delegate.getName(); - } - - @Override - public String getDisplayName() { - return delegate.getDisplayName(); - } - - @Override - public Optional getDescription() { - return delegate.getDescription(); - } - - @Override - public String getType() { - return delegate.getType(); - } - - @Override - public MetricType getTypeRaw() { - return delegate.getTypeRaw(); - } - - @Override - public Optional getUnit() { - return delegate.getUnit(); - } - - @Override - public boolean isReusable() { - return delegate.isReusable(); - } - - @Override - public Map getTags() { - return Collections.emptyMap(); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return (obj != null) - && (this.getClass().isAssignableFrom(obj.getClass())) - && delegate.equals(((InternalMetadataImpl) obj).delegate); - } - - @Override - public String toString() { - return delegate.toString(); - } - -} diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/InternalMetricIDImpl.java b/metrics/metrics/src/main/java/io/helidon/metrics/InternalMetricIDImpl.java deleted file mode 100644 index ba101057fd3..00000000000 --- a/metrics/metrics/src/main/java/io/helidon/metrics/InternalMetricIDImpl.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.metrics; - -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -import io.helidon.common.metrics.InternalBridge; - -import org.eclipse.microprofile.metrics.MetricID; -import org.eclipse.microprofile.metrics.Tag; - -/** - * - */ -class InternalMetricIDImpl implements InternalBridge.MetricID { - - private final MetricID delegate; - - InternalMetricIDImpl(String name) { - delegate = new org.eclipse.microprofile.metrics.MetricID(name); - } - - InternalMetricIDImpl(String name, Map tags) { - delegate = new org.eclipse.microprofile.metrics.MetricID(name, toTagArray(tags)); - } - - private Tag[] toTagArray(Map tags) { - return tags.entrySet().stream() - .map(entry -> new Tag(entry.getKey(), entry.getValue())) - .collect(Collectors.toList()) - .toArray(new Tag[0]); - } - - @Override - public String getName() { - return delegate.getName(); - } - - @Override - public Map getTags() { - return delegate.getTags(); - } - - @Override - public int hashCode() { - int hash = 3; - hash = 37 * hash + Objects.hashCode(this.delegate); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final InternalMetricIDImpl other = (InternalMetricIDImpl) obj; - return Objects.equals(this.delegate, other.delegate); - } - - static class FactoryImpl implements Factory { - - @Override - public io.helidon.common.metrics.InternalBridge.MetricID newMetricID(String name) { - return new InternalMetricIDImpl(name); - } - - @Override - public io.helidon.common.metrics.InternalBridge.MetricID newMetricID(String name, Map tags) { - return new InternalMetricIDImpl(name, tags); - } - } -} diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/MetricsSupport.java b/metrics/metrics/src/main/java/io/helidon/metrics/MetricsSupport.java index 8a2682f5cbf..4cfc6c141ef 100644 --- a/metrics/metrics/src/main/java/io/helidon/metrics/MetricsSupport.java +++ b/metrics/metrics/src/main/java/io/helidon/metrics/MetricsSupport.java @@ -41,14 +41,19 @@ import javax.json.JsonBuilderFactory; import javax.json.JsonObject; import javax.json.JsonObjectBuilder; +import javax.json.JsonStructure; import javax.json.JsonValue; +import io.helidon.common.GenericType; import io.helidon.common.HelidonFeatures; import io.helidon.common.HelidonFlavor; import io.helidon.common.http.Http; import io.helidon.common.http.MediaType; +import io.helidon.common.reactive.Single; import io.helidon.config.Config; -import io.helidon.media.jsonp.server.JsonSupport; +import io.helidon.config.DeprecatedConfig; +import io.helidon.media.common.MessageBodyWriter; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webserver.Handler; import io.helidon.webserver.RequestHeaders; import io.helidon.webserver.Routing; @@ -59,6 +64,7 @@ import io.helidon.webserver.cors.CrossOriginConfig; import org.eclipse.microprofile.metrics.Counter; +import org.eclipse.microprofile.metrics.Metadata; import org.eclipse.microprofile.metrics.Meter; import org.eclipse.microprofile.metrics.Metric; import org.eclipse.microprofile.metrics.MetricID; @@ -104,6 +110,9 @@ public final class MetricsSupport implements Service { private static final String DEFAULT_CONTEXT = "/metrics"; private static final String FEATURE_NAME = "Metrics"; + private static final GenericType JSON_TYPE = GenericType.create(JsonObject.class); + private static final MessageBodyWriter JSONP_WRITER = JsonpSupport.writer(); + static { HelidonFeatures.register(HelidonFlavor.SE, FEATURE_NAME); } @@ -169,7 +178,7 @@ private static void getAll(ServerRequest req, ServerResponse res, Registry regis MediaType mediaType = findBestAccepted(req.headers()); if (mediaType == MediaType.APPLICATION_JSON) { - res.send(toJsonData(registry)); + sendJson(res, toJsonData(registry)); } else if (mediaType == MediaType.TEXT_PLAIN) { res.send(toPrometheusData(registry)); } else { @@ -186,7 +195,7 @@ private void optionsAll(ServerRequest req, ServerResponse res, Registry registry } if (req.headers().isAccepted(MediaType.APPLICATION_JSON)) { - res.send(toJsonMeta(registry)); + sendJson(res, toJsonMeta(registry)); } else { res.status(Http.Status.NOT_ACCEPTABLE_406); res.send(); @@ -342,29 +351,37 @@ public void configureVendorMetrics(String routingName, * tags. */ Registry vendor = rf.getARegistry(MetricRegistry.Type.VENDOR); - Counter totalCount = vendor.counter(new HelidonMetadata(metricPrefix + "count", - "Total number of HTTP requests", - "Each request (regardless of HTTP method) will increase this counter", - MetricType.COUNTER, - MetricUnits.NONE)); - - Meter totalMeter = vendor.meter(new HelidonMetadata(metricPrefix + "meter", - "Meter for overall HTTP requests", - "Each request will mark the meter to see overall throughput", - MetricType.METERED, - MetricUnits.NONE)); - - vendor.counter(new HelidonMetadata("grpc.requests.count", - "Total number of gRPC requests", - "Each gRPC request (regardless of the method) will increase this counter", - MetricType.COUNTER, - MetricUnits.NONE)); - - vendor.meter(new HelidonMetadata("grpc.requests.meter", - "Meter for overall gRPC requests", - "Each gRPC request will mark the meter to see overall throughput", - MetricType.METERED, - MetricUnits.NONE)); + Counter totalCount = vendor.counter(Metadata.builder() + .withName(metricPrefix + "count") + .withDisplayName("Total number of HTTP requests") + .withDescription("Each request (regardless of HTTP method) will increase this counter") + .withType(MetricType.COUNTER) + .withUnit(MetricUnits.NONE) + .build()); + + Meter totalMeter = vendor.meter(Metadata.builder() + .withName(metricPrefix + "meter") + .withDisplayName("Meter for overall HTTP requests") + .withDescription("Each request will mark the meter to see overall throughput") + .withType(MetricType.METERED) + .withUnit(MetricUnits.NONE) + .build()); + + vendor.counter(Metadata.builder() + .withName("grpc.requests.count") + .withDisplayName("Total number of gRPC requests") + .withDescription("Each gRPC request (regardless of the method) will increase this counter") + .withType(MetricType.COUNTER) + .withUnit(MetricUnits.NONE) + .build()); + + vendor.meter(Metadata.builder() + .withName("grpc.requests.meter") + .withDisplayName("Meter for overall gRPC requests") + .withDescription("Each gRPC request will mark the meter to see overall throughput") + .withType(MetricType.METERED) + .withUnit(MetricUnits.NONE) + .build()); rules.any((req, res) -> { totalCount.inc(); @@ -395,9 +412,6 @@ public void configureEndpoint(Routing.Rules rules) { // register the metric registry and factory to be available to all rules.any(new MetricsContextHandler(app, rf)); - rules.anyOf(List.of(Http.Method.GET, Http.Method.OPTIONS), - JsonSupport.create()); - // routing to root of metrics rules.get(context, (req, res) -> getMultiple(req, res, base, app, vendor)) .options(context, (req, res) -> optionsMultiple(req, res, base, app, vendor)); @@ -441,7 +455,7 @@ private void getOne(ServerRequest req, ServerResponse res, Registry registry) { if (mediaType == MediaType.APPLICATION_JSON) { JsonObjectBuilder builder = JSON.createObjectBuilder(); entry.getValue().jsonData(builder, entry.getKey()); - res.send(builder.build()); + sendJson(res, builder.build()); } else if (mediaType == MediaType.TEXT_PLAIN) { final StringBuilder sb = new StringBuilder(); entry.getValue().prometheusData(sb, entry.getKey(), true); @@ -456,10 +470,14 @@ private void getOne(ServerRequest req, ServerResponse res, Registry registry) { }); } + private static void sendJson(ServerResponse res, JsonObject object) { + res.send(JSONP_WRITER.write(Single.just(object), JSON_TYPE, res.writerContext())); + } + private void getMultiple(ServerRequest req, ServerResponse res, Registry... registries) { MediaType mediaType = findBestAccepted(req.headers()); if (mediaType == MediaType.APPLICATION_JSON) { - res.send(toJsonData(registries)); + sendJson(res, toJsonData(registries)); } else if (mediaType == MediaType.TEXT_PLAIN) { res.send(toPrometheusData(registries)); } else { @@ -470,7 +488,7 @@ private void getMultiple(ServerRequest req, ServerResponse res, Registry... regi private void optionsMultiple(ServerRequest req, ServerResponse res, Registry... registries) { if (req.headers().isAccepted(MediaType.APPLICATION_JSON)) { - res.send(toJsonMeta(registries)); + sendJson(res, toJsonMeta(registries)); } else { res.status(Http.Status.NOT_ACCEPTABLE_406); res.send(); @@ -485,7 +503,7 @@ private void optionsOne(ServerRequest req, ServerResponse res, Registry registry if (req.headers().isAccepted(MediaType.APPLICATION_JSON)) { JsonObjectBuilder builder = JSON.createObjectBuilder(); HelidonMetric.class.cast(entry.getKey()).jsonMeta(builder, entry.getValue()); - res.send(builder.build()); + sendJson(res, builder.build()); } else { res.status(Http.Status.NOT_ACCEPTABLE_406); res.send(); @@ -527,10 +545,12 @@ public MetricsSupport build() { */ public Builder config(Config config) { this.config = config; + // align with health checks - config.get("web-context").asString().ifPresent(this::context); - // backward compatibility - config.get("context").asString().ifPresent(this::context); + DeprecatedConfig.get(config, "web-context", "context") + .asString() + .ifPresent(this::webContext); + config.get(CORS_CONFIG_KEY) .as(CrossOriginConfig::create) .ifPresent(this::crossOriginConfig); @@ -547,7 +567,7 @@ public Builder config(Config config) { * {@link RegistryFactory#create(io.helidon.config.Config)} or * {@link RegistryFactory#create()} and create multiple * {@link io.helidon.metrics.MetricsSupport} instances with different - * {@link #context(String) contexts}. + * {@link #webContext(String)} contexts}. *

* If this method is not called, * {@link io.helidon.metrics.MetricsSupport} would use the shared @@ -562,19 +582,6 @@ public Builder registryFactory(RegistryFactory factory) { return this; } - /** - * Set a new root context for REST API of metrics. - * - * @param newContext context to use - * @return updated builder instance - * @deprecated use {@link #webContext(String)} instead, aligned with API - * of heatlh checks - */ - @Deprecated - public Builder context(String newContext) { - return webContext(newContext); - } - /** * Set a new root context for REST API of metrics. * diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/Registry.java b/metrics/metrics/src/main/java/io/helidon/metrics/Registry.java index c676f8fb323..b3f0aee0fb2 100644 --- a/metrics/metrics/src/main/java/io/helidon/metrics/Registry.java +++ b/metrics/metrics/src/main/java/io/helidon/metrics/Registry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,6 @@ import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; -import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -41,7 +40,6 @@ import org.eclipse.microprofile.metrics.Gauge; import org.eclipse.microprofile.metrics.Histogram; import org.eclipse.microprofile.metrics.Metadata; -import org.eclipse.microprofile.metrics.MetadataBuilder; import org.eclipse.microprofile.metrics.Meter; import org.eclipse.microprofile.metrics.Metric; import org.eclipse.microprofile.metrics.MetricFilter; @@ -54,7 +52,7 @@ /** * Metrics registry. */ -public class Registry extends MetricRegistry implements io.helidon.common.metrics.InternalBridge.MetricRegistry { +public class Registry extends MetricRegistry { private static final Tag[] NO_TAGS = new Tag[0]; private static final Map, MetricType> METRIC_TO_TYPE_MAP = prepareMetricToTypeMap(); @@ -108,16 +106,6 @@ public Counter counter(Metadata metadata) { return counter(metadata, NO_TAGS); } - @Override - public Counter counter(io.helidon.common.metrics.InternalBridge.Metadata metadata) { - return counter(toMetadata(metadata)); - } - - @Override - public Counter counter(io.helidon.common.metrics.InternalBridge.Metadata metadata, Map tags) { - return counter(toMetadata(metadata), toTags(tags)); - } - @Override public Counter counter(String name, Tag... tags) { return getOrRegisterMetric(name, HelidonCounter::create, HelidonCounter.class, tags); @@ -138,16 +126,6 @@ public Histogram histogram(Metadata metadata) { return histogram(metadata, NO_TAGS); } - @Override - public Histogram histogram(io.helidon.common.metrics.InternalBridge.Metadata metadata) { - return histogram(toMetadata(metadata)); - } - - @Override - public Histogram histogram(io.helidon.common.metrics.InternalBridge.Metadata metadata, Map tags) { - return histogram(toMetadata(metadata), toTags(tags)); - } - @Override public Histogram histogram(String name, Tag... tags) { return getOrRegisterMetric(name, HelidonHistogram::create, HelidonHistogram.class, tags); @@ -168,16 +146,6 @@ public Meter meter(Metadata metadata) { return meter(metadata, NO_TAGS); } - @Override - public Meter meter(io.helidon.common.metrics.InternalBridge.Metadata metadata) { - return meter(toMetadata(metadata)); - } - - @Override - public Meter meter(io.helidon.common.metrics.InternalBridge.Metadata metadata, Map tags) { - return meter(toMetadata(metadata), toTags(tags)); - } - @Override public Meter meter(String name, Tag... tags) { return getOrRegisterMetric(name, HelidonMeter::create, HelidonMeter.class, tags); @@ -198,16 +166,6 @@ public Timer timer(Metadata metadata) { return timer(metadata, NO_TAGS); } - @Override - public Timer timer(io.helidon.common.metrics.InternalBridge.Metadata metadata) { - return timer(toMetadata(metadata)); - } - - @Override - public Timer timer(io.helidon.common.metrics.InternalBridge.Metadata metadata, Map tags) { - return timer(toMetadata(metadata), toTags(tags)); - } - @Override public Timer timer(String name, Tag... tags) { return getOrRegisterMetric(name, HelidonTimer::create, HelidonTimer.class, tags); @@ -364,81 +322,6 @@ public Map getMetrics() { return Collections.unmodifiableMap(allMetrics); } - @Override - public Map getBridgeMetrics( - Predicate> predicate) { - - final Map result = new HashMap<>(); - - allMetrics.entrySet().stream() - .map(Registry::toBridgeEntry) - .filter(predicate) - .forEach(entry -> result.put(entry.getKey(), entry.getValue())); - return result; - } - - @Override - public Map getBridgeMetrics() { - return getBridgeMetrics(entry -> true); - } - - @Override - public SortedMap getBridgeGauges() { - return getBridgeMetrics(getGauges(), Gauge.class); - } - - @Override - public SortedMap getBridgeCounters() { - return getBridgeMetrics(getCounters(), Counter.class); - } - - @Override - public SortedMap getBridgeHistograms() { - return getBridgeMetrics(getHistograms(), Histogram.class); - } - - @Override - public SortedMap getBridgeMeters() { - return getBridgeMetrics(getMeters(), Meter.class); - } - - @Override - public SortedMap getBridgeTimers() { - return getBridgeMetrics(getTimers(), Timer.class); - } - - private static SortedMap getBridgeMetrics( - SortedMap metrics, Class clazz) { - return metrics.entrySet().stream() - .map(Registry::toBridgeEntry) - .filter(entry -> clazz.isAssignableFrom(entry.getValue().getClass())) - .collect(TreeMap::new, - (map, entry) -> map.put(entry.getKey(), clazz.cast(entry.getValue())), - Map::putAll); - } - - - @Override - public T register( - io.helidon.common.metrics.InternalBridge.Metadata metadata, T metric) throws IllegalArgumentException { - return register(toMetadata(metadata), metric); - } - - @Override - public T register( - io.helidon.common.metrics.InternalBridge.MetricID metricID, T metric) throws IllegalArgumentException { - return register(toMetadata(metricID.getName(), metric), metric, toTags(metricID.getTags())); - } - - private static Map.Entry toBridgeEntry( - Map.Entry entry) { - return new AbstractMap.SimpleEntry<>(new InternalMetricIDImpl( - entry.getKey().getName(), entry.getKey().getTags()), - entry.getValue()); - } - /** * Access a metric by name. Used by FT library. * @@ -449,13 +332,6 @@ public Optional getMetric(String metricName) { return getOptionalMetricEntry(metricName).map(Map.Entry::getValue); } - @Override - public Optional> getBridgeMetric(String metricName) { - return getOptionalMetricEntry(metricName) - .map(Registry::toBridgeEntry); - } - // -- Public not overridden ----------------------------------------------- /** @@ -492,18 +368,6 @@ public String toString() { // -- Package private ----------------------------------------------------- - static Metadata toMetadata(io.helidon.common.metrics.InternalBridge.Metadata metadata) { - final MetadataBuilder builder = new MetadataBuilder(); - builder.withName(metadata.getName()) - .withDisplayName(metadata.getDisplayName()) - .withType(metadata.getTypeRaw()) - .reusable(metadata.isReusable()); - - metadata.getDescription().ifPresent(builder::withDescription); - metadata.getUnit().ifPresent(builder::withUnit); - return builder.build(); - } - Optional> getOptionalMetricEntry(String metricName) { return getOptionalMetricWithIDsEntry(metricName).map(entry -> { final MetricID metricID = entry.getValue().get(0); @@ -649,7 +513,10 @@ private synchronized T getOrRegisterMetric(String metr return getOptionalMetric(metricName, clazz, tags) .orElseGet(() -> { final Metadata metadata = getOrRegisterMetadata(metricName, newType, - () -> new HelidonMetadata(metricName, newType), tags); + () -> Metadata.builder() + .withName(metricName) + .withType(newType) + .build(), tags); return registerMetric(metricName, metricFactory.apply(type.getName(), metadata), tags); }); @@ -675,7 +542,10 @@ private synchronized T registerUniqueMetric(String metricName final MetricType metricType = MetricType.from(metric.getClass()); final Metadata metadata = getOrRegisterMetadata(metricName, metricType, - () -> new HelidonMetadata(metricName, metricType), NO_TAGS); + () -> Metadata.builder() + .withName(metricName) + .withType(metricType) + .build(), NO_TAGS); registerMetric(metricName, toImpl(metadata, metric), NO_TAGS); return metric; } @@ -840,8 +710,8 @@ private static MetricType deriveType(MetricType candidateType, Metric metric) { .orElse(MetricType.INVALID); } - private static HelidonMetadata toMetadata(String name, T metric) { - return new HelidonMetadata(name, toType(metric)); + private static Metadata toMetadata(String name, T metric) { + return Metadata.builder().withName(name).withType(toType(metric)).build(); } private static MetricType toType(Metric metric) { diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/RegistryFactory.java b/metrics/metrics/src/main/java/io/helidon/metrics/RegistryFactory.java index 0d2ef052e78..9cb6c2bb244 100644 --- a/metrics/metrics/src/main/java/io/helidon/metrics/RegistryFactory.java +++ b/metrics/metrics/src/main/java/io/helidon/metrics/RegistryFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or 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,7 +18,6 @@ import java.util.EnumMap; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; import io.helidon.config.Config; @@ -42,7 +41,7 @@ */ // this class is not immutable, as we may need to update registries with configuration post creation // see Github issue #360 -public final class RegistryFactory implements io.helidon.common.metrics.InternalBridge.MetricRegistry.RegistryFactory { +public final class RegistryFactory { private static final RegistryFactory INSTANCE = create(); private final EnumMap registries = new EnumMap<>(Type.class); @@ -85,34 +84,6 @@ public static RegistryFactory create(Config config) { return new RegistryFactory(config); } - - - /** - * Get a supplier for registry factory. The supplier will return the singleton isntance - * that is created. - * - * @return supplier of registry factory (to bind as late as possible) - * @deprecated use {@link io.helidon.metrics.RegistryFactory#getInstance() RegistryFactory::getInstance} instead. - */ - @Deprecated - public static Supplier getRegistryFactory() { - return RegistryFactory::getInstance; - } - - /** - * Create a registry factory for systems without CDI. - * - * @param config configuration to load the factory config from - * @return a new registry factory to obtain application registry (and other registries) - * @deprecated use {@link #create()} or {@link #create(io.helidon.config.Config)} instead when a new - * registry factory instance is needed. Use {@link #getInstance()} or {@link #getInstance(io.helidon.config.Config)} - * to retrieve the shared (singleton) instance. - */ - @Deprecated - public static RegistryFactory createSeFactory(Config config) { - return create(config); - } - /** * Get a singleton instance of the registry factory. * @@ -156,11 +127,6 @@ public MetricRegistry getRegistry(Type type) { return publicRegistries.get(type); } - @Override - public io.helidon.common.metrics.InternalBridge.MetricRegistry getBridgeRegistry(Type type) { - return io.helidon.common.metrics.InternalBridge.MetricRegistry.class.cast(getRegistry(type)); - } - private void update(Config config) { this.config.set(config); } diff --git a/metrics/metrics/src/main/java/module-info.java b/metrics/metrics/src/main/java/module-info.java index 92197c1c702..8880ca985bd 100644 --- a/metrics/metrics/src/main/java/module-info.java +++ b/metrics/metrics/src/main/java/module-info.java @@ -21,19 +21,15 @@ requires java.logging; requires io.helidon.common; - requires io.helidon.common.metrics; requires io.helidon.webserver.cors; requires transitive microprofile.metrics.api; requires java.management; requires io.helidon.webserver; - requires io.helidon.media.jsonp.server; + requires io.helidon.media.jsonp; requires java.json; requires io.helidon.config.mp; requires microprofile.config.api; - provides io.helidon.common.metrics.InternalBridge - with io.helidon.metrics.InternalBridgeImpl; - exports io.helidon.metrics; } diff --git a/metrics/metrics/src/main/resources/META-INF/services/io.helidon.common.metrics.InternalBridge b/metrics/metrics/src/main/resources/META-INF/services/io.helidon.common.metrics.InternalBridge deleted file mode 100644 index 4a6dd4aa007..00000000000 --- a/metrics/metrics/src/main/resources/META-INF/services/io.helidon.common.metrics.InternalBridge +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. -# -# 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. -# - -io.helidon.metrics.InternalBridgeImpl \ No newline at end of file diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/HelidonConcurrentGaugeTest.java b/metrics/metrics/src/test/java/io/helidon/metrics/HelidonConcurrentGaugeTest.java index d75fe29bd2d..58615ef044f 100644 --- a/metrics/metrics/src/test/java/io/helidon/metrics/HelidonConcurrentGaugeTest.java +++ b/metrics/metrics/src/test/java/io/helidon/metrics/HelidonConcurrentGaugeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,11 +63,13 @@ public class HelidonConcurrentGaugeTest { @BeforeAll static void initClass() { - meta = new HelidonMetadata("aConcurrentGauge", - "aConcurrentGauge", - "aConcurrentGauge", - MetricType.CONCURRENT_GAUGE, - MetricUnits.NONE); + meta = Metadata.builder() + .withName("aConcurrentGauge") + .withDisplayName("aConcurrentGauge") + .withDescription("aConcurrentGauge") + .withType(MetricType.CONCURRENT_GAUGE) + .withUnit(MetricUnits.NONE) + .build(); System.out.println("Minimum required seconds within minute is " + MIN_REQUIRED_SECONDS + ", so SECONDS_THRESHOLD is " + SECONDS_THRESHOLD); } diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/HelidonCounterTest.java b/metrics/metrics/src/test/java/io/helidon/metrics/HelidonCounterTest.java index 3c6efec08d1..8ea049057ab 100644 --- a/metrics/metrics/src/test/java/io/helidon/metrics/HelidonCounterTest.java +++ b/metrics/metrics/src/test/java/io/helidon/metrics/HelidonCounterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,11 +47,13 @@ class HelidonCounterTest { @BeforeAll static void initClass() { - meta = new HelidonMetadata("theName", - "theDisplayName", - "theDescription", - MetricType.COUNTER, - MetricUnits.NONE); + meta = Metadata.builder() + .withName("theName") + .withDisplayName("theDisplayName") + .withDescription("theDescription") + .withType(MetricType.COUNTER) + .withUnit(MetricUnits.NONE) + .build(); } @BeforeEach diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/HelidonGaugeTest.java b/metrics/metrics/src/test/java/io/helidon/metrics/HelidonGaugeTest.java index 7a82bc7901c..fbda3ea0c12 100644 --- a/metrics/metrics/src/test/java/io/helidon/metrics/HelidonGaugeTest.java +++ b/metrics/metrics/src/test/java/io/helidon/metrics/HelidonGaugeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or 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,11 +45,13 @@ public class HelidonGaugeTest { @BeforeAll static void initClass() { - meta = new HelidonMetadata("aGauge", - "aGauge", - "aGauge", - MetricType.GAUGE, - MetricUnits.NONE); + meta = Metadata.builder() + .withName("aGauge") + .withDisplayName("aGauge") + .withDescription("aGauge") + .withType(MetricType.GAUGE) + .withUnit(MetricUnits.NONE) + .build(); } @Test diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/HelidonHistogramTest.java b/metrics/metrics/src/test/java/io/helidon/metrics/HelidonHistogramTest.java index 7eb2d92ae79..f0426f8aa0b 100644 --- a/metrics/metrics/src/test/java/io/helidon/metrics/HelidonHistogramTest.java +++ b/metrics/metrics/src/test/java/io/helidon/metrics/HelidonHistogramTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -124,11 +124,13 @@ private static Stream> parsePrometheusText(String prom @BeforeAll static void initClass() { - meta = new HelidonMetadata("file_sizes", - "theDisplayName", - "Users file size", - MetricType.HISTOGRAM, - MetricUnits.KILOBYTES); + meta = Metadata.builder() + .withName("file_sizes") + .withDisplayName("theDisplayName") + .withDescription("Users file size") + .withType(MetricType.HISTOGRAM) + .withUnit(MetricUnits.KILOBYTES) + .build(); histoInt = HelidonHistogram.create("application", meta); histoIntID = new MetricID("file_sizes"); diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/HelidonMeterTest.java b/metrics/metrics/src/test/java/io/helidon/metrics/HelidonMeterTest.java index d0a68274411..25f3951cc95 100644 --- a/metrics/metrics/src/test/java/io/helidon/metrics/HelidonMeterTest.java +++ b/metrics/metrics/src/test/java/io/helidon/metrics/HelidonMeterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,11 +53,13 @@ class HelidonMeterTest { @BeforeAll static void initClass() throws InterruptedException { - Metadata meta = new HelidonMetadata("requests", - "Requests", - "Tracks the number of requests to the server", - MetricType.METERED, - MetricUnits.PER_SECOND); + Metadata meta = Metadata.builder() + .withName("requests") + .withDisplayName("Requests") + .withDescription("Tracks the number of requests to the server") + .withType(MetricType.METERED) + .withUnit(MetricUnits.PER_SECOND) + .build(); LongAdder nanoTime = new LongAdder(); LongAdder milliTime = new LongAdder(); diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/HelidonTimerTest.java b/metrics/metrics/src/test/java/io/helidon/metrics/HelidonTimerTest.java index 2d3126a711c..6cf27b09c4f 100644 --- a/metrics/metrics/src/test/java/io/helidon/metrics/HelidonTimerTest.java +++ b/metrics/metrics/src/test/java/io/helidon/metrics/HelidonTimerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or 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,11 +66,13 @@ class HelidonTimerTest { @BeforeAll static void initClass() { - meta = new HelidonMetadata("response_time", - "Responses", - "Server response time for /index.html", - MetricType.TIMER, - MetricUnits.NANOSECONDS); + meta = Metadata.builder() + .withName("response_time") + .withDisplayName("Responses") + .withDescription("Server response time for /index.html") + .withType(MetricType.TIMER) + .withUnit(MetricUnits.NANOSECONDS) + .build(); dataSetTimer = HelidonTimer.create("application", meta, dataSetTimerClock); dataSetTimerID = new MetricID("response_time"); diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/InternalBridgeTest.java b/metrics/metrics/src/test/java/io/helidon/metrics/InternalBridgeTest.java deleted file mode 100644 index 36325dd2226..00000000000 --- a/metrics/metrics/src/test/java/io/helidon/metrics/InternalBridgeTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. - * - * 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.metrics; - -import java.util.AbstractMap; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.SortedMap; -import java.util.stream.Collectors; - -import io.helidon.common.metrics.InternalBridge; -import io.helidon.common.metrics.InternalBridge.Metadata; - -import org.eclipse.microprofile.metrics.Counter; -import org.eclipse.microprofile.metrics.MetricID; -import org.eclipse.microprofile.metrics.MetricRegistry; -import org.eclipse.microprofile.metrics.MetricType; -import org.eclipse.microprofile.metrics.MetricUnits; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; - -/** - * - */ -public class InternalBridgeTest { - - private static io.helidon.common.metrics.InternalBridge ib; - private static io.helidon.common.metrics.InternalBridge.MetricRegistry.RegistryFactory ibFactory; - private static RegistryFactory factory; - private static io.helidon.common.metrics.InternalBridge.MetricRegistry ibVendor; - private static MetricRegistry vendor; - private static io.helidon.common.metrics.InternalBridge.MetricRegistry ibApp; - private static MetricRegistry app; - - public InternalBridgeTest() { - } - - @BeforeAll - private static void loadFactory() { - ib = io.helidon.common.metrics.InternalBridge.INSTANCE; - ibFactory = ib.getRegistryFactory(); - factory = RegistryFactory.getInstance(); - ibVendor = ibFactory.getBridgeRegistry(MetricRegistry.Type.VENDOR); - vendor = factory.getRegistry(MetricRegistry.Type.VENDOR); - ibApp = ibFactory.getBridgeRegistry(MetricRegistry.Type.APPLICATION); - app = factory.getRegistry(MetricRegistry.Type.APPLICATION); - } - - @Test - public void testBridgeRegistryFactory() { - assertSame(factory, ibFactory, "Factory and neutral factory do not match"); - assertSame(vendor, ibVendor, "Vendor registries via the two factories do not match"); - } - - @Test - public void testTags() { - String globalTags = System.getenv(MetricID.GLOBAL_TAGS_VARIABLE); - Map expectedTags = new HashMap<>(); - expectedTags.put("t1", "one"); - expectedTags.put("t2", "two"); - - if (globalTags != null) { - Arrays.stream(globalTags.split(",")) - .map(expr -> { - final int eq = expr.indexOf("="); - if (eq <= 0) { - return null; - } - String tag = expr.substring(0, eq); - String value = expr.substring(eq + 1); - return new AbstractMap.SimpleEntry<>(tag, value); - }) - .filter(entry -> entry != null) - .forEach(entry -> expectedTags.put(entry.getKey(), entry.getValue())); - - } - - org.eclipse.microprofile.metrics.Tag[] expectedTagsArray = - expectedTags.entrySet().stream() - .map(entry -> new org.eclipse.microprofile.metrics.Tag(entry.getKey(), entry.getValue())) - .collect(Collectors.toList()) - .toArray(new org.eclipse.microprofile.metrics.Tag[0]); - - Metadata internalMetadata = InternalBridge.newMetadataBuilder() - .withName("MyCounter") - .withDisplayName("MyCounter display") - .withDescription("This is a test counter") - .withType(MetricType.COUNTER) - .withUnit(MetricUnits.NONE) - .build(); - - org.eclipse.microprofile.metrics.Metadata metadata = new org.eclipse.microprofile.metrics.MetadataBuilder() - .withName("MyCounter") - .withDisplayName("MyCounter display") - .withDescription("This is a test counter") - .withType(MetricType.COUNTER) - .withUnit(MetricUnits.NONE) - .build(); - - Counter counter = ibApp.counter(internalMetadata, expectedTags); - - org.eclipse.microprofile.metrics.MetricID expectedMetricID = - new org.eclipse.microprofile.metrics.MetricID("MyCounter", expectedTagsArray); - - SortedMap matchedCounters = app.getCounters( - (metricID, metric) -> metricID.getName().equals("MyCounter")); - - assertEquals(1, matchedCounters.size()); - assertEquals(counter, matchedCounters.get(expectedMetricID)); - - } -} diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/MetricImplTest.java b/metrics/metrics/src/test/java/io/helidon/metrics/MetricImplTest.java index 39257ca0f8e..ad3ace1d05c 100644 --- a/metrics/metrics/src/test/java/io/helidon/metrics/MetricImplTest.java +++ b/metrics/metrics/src/test/java/io/helidon/metrics/MetricImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,11 +69,13 @@ class MetricImplTest { @BeforeAll public static void initClass() { - Metadata meta = new HelidonMetadata("theName", - "theDisplayName", - "theDescription", - MetricType.COUNTER, - MetricUnits.NONE); + Metadata meta = Metadata.builder() + .withName("theName") + .withDisplayName("theDisplayName") + .withDescription("theDescription") + .withType(MetricType.COUNTER) + .withUnit(MetricUnits.NONE) + .build(); impl = new MetricImpl("base", meta) { @Override @@ -88,7 +90,10 @@ public void jsonData(JsonObjectBuilder builder, MetricID metricID) { }; implID = new MetricID(meta.getName()); - meta = new HelidonMetadata("counterWithoutDescription", MetricType.COUNTER); + meta = Metadata.builder() + .withName("counterWithoutDescription") + .withType(MetricType.COUNTER) + .build(); implWithoutDescription = new MetricImpl("base", meta) { @Override diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/RegistryTest.java b/metrics/metrics/src/test/java/io/helidon/metrics/RegistryTest.java index cc575be67ed..018b131d2c6 100644 --- a/metrics/metrics/src/test/java/io/helidon/metrics/RegistryTest.java +++ b/metrics/metrics/src/test/java/io/helidon/metrics/RegistryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,8 +60,14 @@ void testSameIDDifferentType() { @Test void testSameNameDifferentTagsDifferentTypes() { - Metadata metadata1 = new HelidonMetadata("counter2", MetricType.COUNTER); - Metadata metadata2 = new HelidonMetadata("counter2", MetricType.TIMER); + Metadata metadata1 = Metadata.builder() + .withName("counter2") + .withType(MetricType.COUNTER) + .build(); + Metadata metadata2 = Metadata.builder() + .withName("counter2") + .withType(MetricType.TIMER) + .build(); registry.counter(metadata1, tag1); IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> registry.timer(metadata2, tag2)); @@ -70,10 +76,22 @@ void testSameNameDifferentTagsDifferentTypes() { @Test void testIncompatibleReuseNoTags() { - Metadata metadata1 = new HelidonMetadata("counter3", "display name", - "description", MetricType.COUNTER, MetricUnits.NONE, true); - Metadata metadata2 = new HelidonMetadata("counter3", "display name", - "description", MetricType.COUNTER, MetricUnits.NONE, false); + Metadata metadata1 = Metadata.builder() + .withName("counter3") + .withDisplayName("display name") + .withDescription("description") + .withType(MetricType.COUNTER) + .withUnit(MetricUnits.NONE) + .reusable(true) + .build(); + Metadata metadata2 = Metadata.builder() + .withName("counter3") + .withDisplayName("display name") + .withDescription("description") + .withType(MetricType.COUNTER) + .withUnit(MetricUnits.NONE) + .reusable(false) + .build(); registry.counter(metadata1); IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, @@ -83,10 +101,22 @@ void testIncompatibleReuseNoTags() { @Test void testIncompatibleReuseWithTags() { - Metadata metadata1 = new HelidonMetadata("counter4", "display name", - "description", MetricType.COUNTER, MetricUnits.NONE, true); - Metadata metadata2 = new HelidonMetadata("counter4", "display name", - "description", MetricType.COUNTER, MetricUnits.NONE, false); + Metadata metadata1 = Metadata.builder() + .withName("counter4") + .withDisplayName("display name") + .withDescription("description") + .withType(MetricType.COUNTER) + .withUnit(MetricUnits.NONE) + .reusable(true) + .build(); + Metadata metadata2 = Metadata.builder() + .withName("counter4") + .withDisplayName("display name") + .withDescription("description") + .withType(MetricType.COUNTER) + .withUnit(MetricUnits.NONE) + .reusable(false) + .build(); registry.counter(metadata1, tag1); IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, @@ -96,10 +126,22 @@ void testIncompatibleReuseWithTags() { @Test void testSameIDSameReuseDifferentOtherMetadata() { - Metadata metadata1 = new HelidonMetadata("counter5", "display name", - "description", MetricType.COUNTER, MetricUnits.NONE, true); - Metadata metadata2 = new HelidonMetadata("counter5", "OTHER display name", - "description", MetricType.COUNTER, MetricUnits.NONE, true); + Metadata metadata1 = Metadata.builder() + .withName("counter5") + .withDisplayName("display name") + .withDescription("description") + .withType(MetricType.COUNTER) + .withUnit(MetricUnits.NONE) + .reusable(true) + .build(); + Metadata metadata2 = Metadata.builder() + .withName("counter5") + .withDisplayName("OTHER display name") + .withDescription("description") + .withType(MetricType.COUNTER) + .withUnit(MetricUnits.NONE) + .reusable(true) + .build(); registry.counter(metadata1, tag1); IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, diff --git a/microprofile/access-log/pom.xml b/microprofile/access-log/pom.xml index c438fa8919e..e0c65b0ed4b 100644 --- a/microprofile/access-log/pom.xml +++ b/microprofile/access-log/pom.xml @@ -1,7 +1,7 @@ - io.helidon.media.jsonp - helidon-media-jsonp-common + io.helidon.media + helidon-media-jsonp runtime diff --git a/microprofile/server/src/main/java/io/helidon/microprofile/server/JaxRsCdiExtension.java b/microprofile/server/src/main/java/io/helidon/microprofile/server/JaxRsCdiExtension.java index 4708f228374..9dfdb760085 100644 --- a/microprofile/server/src/main/java/io/helidon/microprofile/server/JaxRsCdiExtension.java +++ b/microprofile/server/src/main/java/io/helidon/microprofile/server/JaxRsCdiExtension.java @@ -41,6 +41,7 @@ import javax.ws.rs.core.Application; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; import io.helidon.common.HelidonFeatures; import io.helidon.common.HelidonFlavor; @@ -66,6 +67,7 @@ public class JaxRsCdiExtension implements Extension { private final Set> applications = new LinkedHashSet<>(); private final Set> resources = new HashSet<>(); + private final Set> providers = new HashSet<>(); private final AtomicBoolean setInStone = new AtomicBoolean(false); private void collectApplications(@Observes ProcessAnnotatedType applicationType) { @@ -82,6 +84,18 @@ private void collectResourceClasses(@Observes @WithAnnotations(Path.class) Proce resources.add(resourceClass); } + private void collectProviderClasses(@Observes @WithAnnotations(Provider.class) ProcessAnnotatedType providerType) { + Class providerClass = providerType.getAnnotatedType().getJavaClass(); + if (providerClass.isInterface()) { + // we are only interested in classes + LOGGER.finest(() -> "Discovered @Provider interface " + providerClass + .getName() + ", ignored as we only support classes"); + return; + } + LOGGER.finest(() -> "Discovered @Provider class " + providerClass.getName()); + providers.add(providerClass); + } + // once application scoped starts, we do not allow modification of applications void fixApps(@Observes @Priority(PLATFORM_BEFORE) @Initialized(ApplicationScoped.class) Object event) { this.setInStone.set(true); @@ -102,10 +116,16 @@ public List applicationsToRun() throws IllegalStateException { throw new IllegalStateException("Applications are not yet fixed. This method is only available in " + "@Initialized(ApplicationScoped.class) event, before server is started"); } + + // set of resource and provider classes that were discovered + Set> allClasses = new HashSet<>(); + allClasses.addAll(resources); + allClasses.addAll(providers); + if (applications.isEmpty() && applicationMetas.isEmpty()) { // create a synthetic application from all resource classes if (!resources.isEmpty()) { - addSyntheticApp(resources); + addSyntheticApp(allClasses); } } @@ -114,7 +134,7 @@ public List applicationsToRun() throws IllegalStateException { .stream() .map(appClass -> JaxRsApplication.builder() .applicationClass(appClass) - .config(ResourceConfig.forApplicationClass(appClass, resources)) + .config(ResourceConfig.forApplicationClass(appClass, allClasses)) .build()) .collect(Collectors.toList())); @@ -238,7 +258,7 @@ public void addSyntheticApplication(List> resourceClasses) throws Illeg // set-up synthetic application from resource classes private void addSyntheticApp(Collection> resourceClasses) { // the classes set must be created before the lambda, as the incoming collection may be mutable - Set> classes = new HashSet<>(resourceClasses); + Set> classes = Set.copyOf(resourceClasses); this.applicationMetas.add(JaxRsApplication.builder() .synthetic(true) .applicationClass(Application.class) diff --git a/microprofile/server/src/main/java/io/helidon/microprofile/server/Main.java b/microprofile/server/src/main/java/io/helidon/microprofile/server/Main.java index ba2e089a59e..0d49898f62e 100644 --- a/microprofile/server/src/main/java/io/helidon/microprofile/server/Main.java +++ b/microprofile/server/src/main/java/io/helidon/microprofile/server/Main.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ * Uses {@code logging.properties} to configure Java logging unless a configuration is defined through * a Java system property. The file is expected either in the directory the application was started, or on * the classpath. - * @deprecated use {@link io.helidon.microprofile.cdi.Main} instead + * @deprecated since 2.0.0, use {@link io.helidon.microprofile.cdi.Main} instead */ @Deprecated public final class Main { @@ -35,7 +35,7 @@ private Main() { } /** - * Main method to start server. The server will collection JAX-RS application automatically (through + * Main method to start server. The server will collect JAX-RS application automatically (through * CDI extension - just annotate it with {@link javax.enterprise.context.ApplicationScoped}). * * @param args command line arguments, currently ignored diff --git a/microprofile/server/src/main/java/io/helidon/microprofile/server/Server.java b/microprofile/server/src/main/java/io/helidon/microprofile/server/Server.java index 4cfea7e8918..0b2c26faa86 100644 --- a/microprofile/server/src/main/java/io/helidon/microprofile/server/Server.java +++ b/microprofile/server/src/main/java/io/helidon/microprofile/server/Server.java @@ -20,7 +20,6 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutorService; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import java.util.logging.Logger; @@ -133,8 +132,6 @@ final class Builder { // this constant is to ensure we initialize Helidon CDI at build time private static final HelidonContainer CONTAINER = HelidonContainer.instance(); - - static final AtomicBoolean IN_PROGRESS_OR_RUNNING = new AtomicBoolean(); private static final Logger STARTUP_LOGGER = Logger.getLogger("io.helidon.microprofile.startup.builder"); private final List> resourceClasses = new LinkedList<>(); @@ -148,11 +145,29 @@ final class Builder { private boolean retainDiscovered = false; private Builder() { - if (!IN_PROGRESS_OR_RUNNING.compareAndSet(false, true)) { - throw new IllegalStateException("There is another builder in progress, or another Server running. " - + "You cannot run more than one in parallel"); - } LOGGER.finest(() -> "Container context id: " + CONTAINER.context().id()); + + ServerCdiExtension server = null; + try { + server = CDI.current() + .getBeanManager() + .getExtension(ServerCdiExtension.class); + } catch (IllegalStateException ignored) { + // CDI is not started, so we should be fine + } + + if (null != server) { + if (server.started()) { + throw new IllegalStateException("Server is already started. Maybe you have initialized CDI yourself? " + + "If you do so, then you cannot use Server.builder() to set up " + + "your server. " + + "Config is initialized with defaults or using " + + "meta-config.yaml; applications are discovered using CDI. " + + "To use custom configuration, you can use " + + "ConfigProviderResolver.instance().registerConfig(config, " + + "classLoader);"); + } + } } /** @@ -162,32 +177,21 @@ private Builder() { * @throws MpException in case the server fails to be created */ public Server build() { - // make sure server is not already running - try { - ServerCdiExtension server = CDI.current() - .getBeanManager() - .getExtension(ServerCdiExtension.class); + // we may have shutdown the original instance, this is to ensure we use the current CDI. + HelidonContainer instance = HelidonContainer.instance(); - if (server.started()) { - SeContainer container = (SeContainer) CDI.current(); - container.close(); - throw new MpException("Server is already started. Maybe you have initialized CDI yourself? " - + "If you do so, then you cannot use Server.builder() to set up your server. " - + "Config is initialized with defaults or using " - + "meta-config.yaml; applications are discovered using CDI. " - + "To use custom configuration, you can use " - + "ConfigProviderResolver.instance().registerConfig(config, " - + "classLoader);"); + try { + // now run the build within context already + return Contexts.runInContext(instance.context(), this::doBuild); + } catch (Exception e) { + try { + ((SeContainer) CDI.current()).close(); + } catch (IllegalStateException ignored) { + // no cdi registered } - } catch (IllegalStateException ignored) { - // container is not running - server cannot be started in such a case - // ignore this - } - // we may have shutdown the original instance, this is to ensure we use the current CDI. - HelidonContainer instance = HelidonContainer.instance(); - // now run the build within context already - return Contexts.runInContext(instance.context(), this::doBuild); + throw e; + } } private Server doBuild() { diff --git a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java index a40b2cea73c..954f95f11b0 100644 --- a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java +++ b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java @@ -28,6 +28,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -49,6 +50,7 @@ import io.helidon.common.configurable.ServerThreadPoolSupplier; import io.helidon.common.http.Http; import io.helidon.config.Config; +import io.helidon.microprofile.cdi.BuildTimeStart; import io.helidon.microprofile.cdi.RuntimeStart; import io.helidon.webserver.Routing; import io.helidon.webserver.Service; @@ -70,6 +72,7 @@ public class ServerCdiExtension implements Extension { } private static final Logger LOGGER = Logger.getLogger(ServerCdiExtension.class.getName()); + private static final AtomicBoolean IN_PROGRESS_OR_RUNNING = new AtomicBoolean(); // build time private WebServer.Builder serverBuilder = WebServer.builder() @@ -92,6 +95,15 @@ public class ServerCdiExtension implements Extension { private volatile boolean started; private final List jerseySupports = new LinkedList<>(); + private void buildTime(@Observes @BuildTimeStart Object event) { + // update the status of server, as we may have been started without a builder being used + // such as when cdi.Main or SeContainerInitializer are used + if (!IN_PROGRESS_OR_RUNNING.compareAndSet(false, true)) { + throw new IllegalStateException("There is another builder in progress, or another Server running. " + + "You cannot run more than one in parallel"); + } + } + private void prepareRuntime(@Observes @RuntimeStart Config config) { serverBuilder.config(config.get("server")); this.config = config; @@ -216,18 +228,30 @@ private void registerClasspathStaticContent(Config config) { } private void stopServer(@Observes @Priority(PLATFORM_BEFORE) @BeforeDestroyed(ApplicationScoped.class) Object event) { - if (null == webserver) { + try { + if (started) { + doStop(event); + } + } finally { + // as there only can be a single CDI in a single JVM, once this CDI is shutting down, we + // can start another one + IN_PROGRESS_OR_RUNNING.set(false); + } + } + + private void doStop(Object event) { + if (null == webserver || !started) { // nothing to do return; } long beforeT = System.nanoTime(); - System.out.println("Stopping WebServer for " + event); try { webserver.shutdown() .toCompletableFuture() .get(); + started = false; jerseySupports.forEach(JerseySupport::close); } catch (InterruptedException | ExecutionException e) { LOGGER.log(Level.SEVERE, "Failed to stop web server", e); diff --git a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerImpl.java b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerImpl.java index ba802a446c5..90d66d32b07 100644 --- a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerImpl.java +++ b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerImpl.java @@ -26,13 +26,10 @@ import io.helidon.microprofile.cdi.HelidonContainer; -import static io.helidon.microprofile.server.Server.Builder.IN_PROGRESS_OR_RUNNING; - /** * Server to handle lifecycle of microprofile implementation. */ public class ServerImpl implements Server { - private static final Logger LOGGER = Logger.getLogger(ServerImpl.class.getName()); private static final Logger STARTUP_LOGGER = Logger.getLogger("io.helidon.microprofile.startup.server"); private final HelidonContainer helidonContainer = HelidonContainer.instance(); @@ -97,7 +94,6 @@ public Server start() { @Override public Server stop() { container.close(); - IN_PROGRESS_OR_RUNNING.set(false); return this; } diff --git a/microprofile/server/src/test/java/io/helidon/microprofile/server/ParallelRunTest.java b/microprofile/server/src/test/java/io/helidon/microprofile/server/ParallelRunTest.java index 5eeac4a42b4..406fb066604 100644 --- a/microprofile/server/src/test/java/io/helidon/microprofile/server/ParallelRunTest.java +++ b/microprofile/server/src/test/java/io/helidon/microprofile/server/ParallelRunTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or 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,7 +16,18 @@ package io.helidon.microprofile.server; +import java.util.Map; + +import javax.enterprise.inject.se.SeContainerInitializer; + +import io.helidon.config.mp.MpConfigSources; +import io.helidon.microprofile.cdi.Main; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -26,24 +37,90 @@ * Running multiple servers in parallel. */ class ParallelRunTest { - private Server server; + private static ConfigProviderResolver resolver; + private static Config emptyConfig; + private static ClassLoader cl; + private static Config originalConfig; + + @BeforeAll + public static void initClass() { + resolver = ConfigProviderResolver.instance(); + originalConfig = resolver.getConfig(); + emptyConfig = resolver.getBuilder().build(); + cl = Thread.currentThread().getContextClassLoader(); + } + + @AfterAll + public static void destroyClass() { + resolver.registerConfig(originalConfig, cl); + } @BeforeEach - void startFirstServer() { - server = Server.builder() + @AfterEach + public void resetConfig() { + resolver.registerConfig(emptyConfig, cl); + } + + @Test + void testParallelFails() { + Server server = Server.builder() .port(0) + .build() + .start(); + + try { + assertThrows(IllegalStateException.class, Server::builder); + } finally { + server.stop(); + } + } + + @Test + void testParallelCdiFails() { + Config config = resolver.getBuilder() + .withSources(MpConfigSources.create(Map.of("server.port", "0", + "mp.initializer.allow", "true"))) .build(); + resolver.registerConfig(config, cl); - server.start(); + try { + Main.main(new String[0]); + assertThrows(IllegalStateException.class, () -> SeContainerInitializer.newInstance() + .initialize()); + } finally { + Main.shutdown(); + } } - @AfterEach - void stopFirstServer() { - server.stop(); + @Test + void testParallelContainerInitializerFails() { + Config config = resolver.getBuilder() + .withSources(MpConfigSources.create(Map.of("server.port", "0", + "mp.initializer.allow", "true"))) + .build(); + resolver.registerConfig(config, cl); + + try { + SeContainerInitializer.newInstance().initialize(); + assertThrows(IllegalStateException.class, () -> SeContainerInitializer.newInstance() + .initialize()); + } finally { + Main.shutdown(); + } } @Test - void testParallelFails() { - assertThrows(IllegalStateException.class, Server::builder); + void testParallelWithCdiFails() { + Config config = resolver.getBuilder() + .withSources(MpConfigSources.create(Map.of("server.port", "0"))) + .build(); + resolver.registerConfig(config, cl); + + try { + Main.main(new String[0]); + assertThrows(IllegalStateException.class, Server::builder); + } finally { + Main.shutdown(); + } } } diff --git a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerExtension.java b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerExtension.java index cadc7131317..f78ecc583d6 100644 --- a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerExtension.java +++ b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,7 +48,7 @@ static class HelidonCDIInjectionEnricher extends CDIInjectionEnricher { @Override public BeanManager getBeanManager() { if (beanManager == null) { - CDI cdi = CDI.current(); + CDI cdi = cdi(); if (cdi != null) { SeContainer container = (SeContainer) cdi; if (container.isRunning()) { @@ -66,13 +66,21 @@ public CreationalContext getCreationalContext() { public RequestContextController getRequestContextController() { if (requestContextController == null) { - CDI cdi = CDI.current(); + CDI cdi = cdi(); if (cdi != null) { requestContextController = cdi.select(RequestContextController.class).get(); } } return requestContextController; } + + private static CDI cdi() { + try { + return CDI.current(); + } catch (IllegalStateException ignored) { + return null; + } + } } /** diff --git a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java index 81b8767c8ab..c8b36daabe0 100644 --- a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java +++ b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or 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,7 +45,6 @@ import io.helidon.config.mp.MpConfigSources; import io.helidon.microprofile.server.Server; -import io.helidon.microprofile.server.ServerCdiExtension; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; @@ -107,24 +106,10 @@ public void setup(HelidonContainerConfiguration configuration) { @Override public void start() { - try { - if (!CDI.current() - .getBeanManager() - .getExtension(ServerCdiExtension.class) - .started()) { - dummyServer = Server.builder().build(); - } - } catch (IllegalStateException e) { - // CDI not running - dummyServer = Server.builder().build(); - } } @Override public void stop() { - if (null != dummyServer) { - dummyServer.stop(); - } } @Override @@ -186,9 +171,13 @@ public ProtocolMetaData deploy(Archive archive) throws DeploymentException { void startServer(RunContext context, Path[] classPath) throws ReflectiveOperationException { - Optional.ofNullable((SeContainer) CDI.current()) - .ifPresent(SeContainer::close); - stopAll(); + try { + Optional.of((SeContainer) CDI.current()) + .ifPresent(SeContainer::close); + stopAll(); + } catch (IllegalStateException ignored) { + // there is no server running + } context.classLoader = new MyClassloader(new URLClassLoader(toUrls(classPath))); @@ -353,7 +342,6 @@ void stopAll() { polled.run(); polled = STOP_RUNNABLES.poll(); } - dummyServer.stop(); } @Override diff --git a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java index 6b96ae39831..1450956893f 100644 --- a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java +++ b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java @@ -18,12 +18,9 @@ import java.util.logging.Logger; -import javax.enterprise.inject.se.SeContainer; -import javax.enterprise.inject.spi.CDI; import javax.ws.rs.ApplicationPath; import io.helidon.microprofile.server.Server; -import io.helidon.microprofile.server.ServerCdiExtension; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; @@ -83,24 +80,6 @@ public void stop() { if (null != server) { LOGGER.finest(() -> "Stopping server"); server.stop(); - } else { - //emergency cleanup see #1446 - stopCdiContainer(); - } - } - - private static void stopCdiContainer() { - try { - ServerCdiExtension server = CDI.current() - .getBeanManager() - .getExtension(ServerCdiExtension.class); - - if (server.started()) { - SeContainer container = (SeContainer) CDI.current(); - container.close(); - } - } catch (IllegalStateException e) { - //noop container is not running } } } diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingContextFilter.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingContextFilter.java index 75538326f8f..4070a08ebcc 100644 --- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingContextFilter.java +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingContextFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or 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,6 +15,8 @@ */ package io.helidon.microprofile.tracing; +import java.util.Optional; + import javax.annotation.Priority; import javax.enterprise.context.ApplicationScoped; import javax.inject.Provider; @@ -56,11 +58,11 @@ public void filter(ContainerRequestContext requestContext) { ServerRequest serverRequest = this.request.get(); Tracer tracer = serverRequest.tracer(); - SpanContext parentSpan = serverRequest.spanContext(); + Optional parentSpan = serverRequest.spanContext(); boolean clientEnabled = config.getOptionalValue("tracing.client.enabled", Boolean.class).orElse(true); TracingContext tracingContext = TracingContext.create(tracer, serverRequest.headers().toMap(), clientEnabled); - tracingContext.parentSpan(parentSpan); + parentSpan.ifPresent(tracingContext::parentSpan); Contexts.context().ifPresent(ctx -> ctx.register(tracingContext)); } diff --git a/openapi/pom.xml b/openapi/pom.xml index 2ae5e0ad00b..b6a7534cb7e 100644 --- a/openapi/pom.xml +++ b/openapi/pom.xml @@ -138,8 +138,8 @@ - io.helidon.media.jsonp - helidon-media-jsonp-server + io.helidon.media + helidon-media-jsonp jakarta.json diff --git a/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java b/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java index aec754f5de7..7bc696de6ff 100644 --- a/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java +++ b/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java @@ -54,7 +54,9 @@ import io.helidon.common.http.Http; import io.helidon.common.http.MediaType; import io.helidon.config.Config; -import io.helidon.media.jsonp.server.JsonSupport; +import io.helidon.media.common.MessageBodyReaderContext; +import io.helidon.media.common.MessageBodyWriterContext; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.openapi.internal.OpenAPIConfigImpl; import io.helidon.webserver.Routing; import io.helidon.webserver.ServerRequest; @@ -156,11 +158,18 @@ public void update(Routing.Rules rules) { */ public void configureEndpoint(Routing.Rules rules) { - rules.get(JsonSupport.create()) + rules.get(this::registerJsonpSupport) .any(webContext, corsEnabledServiceHelper.processor()) .get(webContext, this::prepareResponse); } + private void registerJsonpSupport(ServerRequest req, ServerResponse res) { + MessageBodyReaderContext readerContext = req.content().readerContext(); + MessageBodyWriterContext writerContext = res.writerContext(); + JsonpSupport.create().register(readerContext, writerContext); + req.next(); + } + static synchronized SnakeYAMLParserHelper helper() { if (helper == null) { helper = SnakeYAMLParserHelper.create(ExpandedTypeDescription::create); diff --git a/openapi/src/main/java/module-info.java b/openapi/src/main/java/module-info.java index 297187d002b..b8adfe9170d 100644 --- a/openapi/src/main/java/module-info.java +++ b/openapi/src/main/java/module-info.java @@ -21,7 +21,7 @@ requires io.helidon.common; requires io.helidon.config; requires io.helidon.media.common; - requires io.helidon.media.jsonp.server; + requires io.helidon.media.jsonp; requires io.helidon.webserver; requires io.helidon.webserver.cors; diff --git a/pom.xml b/pom.xml index f04e21d6edf..bdb62594d6a 100644 --- a/pom.xml +++ b/pom.xml @@ -93,7 +93,7 @@ 1.6.0 2.19.1 2.3 - 2.0.0-M3 + 2.0.0-RC1 ${version.lib.hibernate} 0.8.5 1.0.6 diff --git a/security/integration/common/src/main/java/io/helidon/security/integration/common/CommonTracing.java b/security/integration/common/src/main/java/io/helidon/security/integration/common/CommonTracing.java index 31d112845f9..bc87dff4478 100644 --- a/security/integration/common/src/main/java/io/helidon/security/integration/common/CommonTracing.java +++ b/security/integration/common/src/main/java/io/helidon/security/integration/common/CommonTracing.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -106,21 +106,6 @@ public Optional findParent() { .or(() -> parentSpanContext); } - /** - * Find closes parent span. - * - * @return span context if found - * @deprecated will be removed once security context works - * with {@link io.opentracing.SpanContext}. Needed for - * backward compatibility - */ - @Deprecated - public Optional findParentSpan() { - Optional closest = closestSecuritySpan(); - - return closest.or(() -> parentSpan); - } - /** * Log response status. * This is to be used by authorization, authentication and outbound diff --git a/security/integration/grpc/src/main/java/io/helidon/security/integration/grpc/GrpcClientSecurity.java b/security/integration/grpc/src/main/java/io/helidon/security/integration/grpc/GrpcClientSecurity.java index 56c629bb91d..2d29b5a84e5 100644 --- a/security/integration/grpc/src/main/java/io/helidon/security/integration/grpc/GrpcClientSecurity.java +++ b/security/integration/grpc/src/main/java/io/helidon/security/integration/grpc/GrpcClientSecurity.java @@ -90,7 +90,6 @@ public void applyRequestMetadata(RequestInfo requestInfo, Executor appExecutor, OutboundSecurityClientBuilder clientBuilder = context.outboundClientBuilder() .outboundEnvironment(outboundEnv) .tracingSpan(tracing.findParent().orElse(null)) - .tracingSpan(tracing.findParentSpan().orElse(null)) .outboundEndpointConfig(outboundEp) .explicitProvider(explicitProvider); diff --git a/security/integration/grpc/src/main/java/io/helidon/security/integration/grpc/GrpcSecurityHandler.java b/security/integration/grpc/src/main/java/io/helidon/security/integration/grpc/GrpcSecurityHandler.java index e24fc47a89d..61598836620 100644 --- a/security/integration/grpc/src/main/java/io/helidon/security/integration/grpc/GrpcSecurityHandler.java +++ b/security/integration/grpc/src/main/java/io/helidon/security/integration/grpc/GrpcSecurityHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,7 +60,6 @@ import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; import io.grpc.Status; -import io.opentracing.Span; import io.opentracing.SpanContext; import static io.helidon.security.AuditEvent.AuditParam.plain; @@ -457,8 +456,7 @@ private CompletionStage processAuthentication(ServerCall call, SecurityClientBuilder clientBuilder = securityContext.atnClientBuilder(); configureSecurityRequest(clientBuilder, - atnTracing.findParent().orElse(null), - atnTracing.findParentSpan().orElse(null)); + atnTracing.findParent().orElse(null)); clientBuilder.explicitProvider(explicitAuthenticator.orElse(null)).submit().thenAccept(response -> { switch (response.status()) { @@ -534,12 +532,10 @@ private void atnFinish(CompletableFuture future) { } private void configureSecurityRequest(SecurityRequestBuilder> request, - SpanContext parentSpanContext, - Span parentSpan) { + SpanContext parentSpanContext) { request.optional(authenticationOptional.orElse(false)) - .tracingSpan(parentSpanContext) - .tracingSpan(parentSpan); + .tracingSpan(parentSpanContext); } private CompletionStage processAuthorization( @@ -577,8 +573,7 @@ private CompletionStage processAuthorization( client = context.atzClientBuilder(); configureSecurityRequest(client, - atzTracing.findParent().orElse(null), - atzTracing.findParentSpan().orElse(null)); + atzTracing.findParent().orElse(null)); client.explicitProvider(explicitAuthorizer.orElse(null)).submit().thenAccept(response -> { atzTracing.logStatus(response.status()); diff --git a/security/integration/jersey-client/src/main/java/io/helidon/security/integration/jersey/client/ClientSecurityFilter.java b/security/integration/jersey-client/src/main/java/io/helidon/security/integration/jersey/client/ClientSecurityFilter.java index 5f12a85a1e0..4a61373af3e 100644 --- a/security/integration/jersey-client/src/main/java/io/helidon/security/integration/jersey/client/ClientSecurityFilter.java +++ b/security/integration/jersey-client/src/main/java/io/helidon/security/integration/jersey/client/ClientSecurityFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or 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,7 +29,6 @@ import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.Provider; import io.helidon.common.HelidonFeatures; import io.helidon.common.context.Contexts; @@ -48,7 +47,6 @@ * Only works as part of integration with Security component. * This class is public to allow unit testing from providers (without invoking an HTTP request) */ -@Provider @ConstrainedTo(RuntimeType.CLIENT) @Priority(Priorities.AUTHENTICATION) public class ClientSecurityFilter implements ClientRequestFilter { @@ -114,7 +112,6 @@ private void outboundSecurity(ClientRequestContext requestContext, SecurityConte OutboundSecurityClientBuilder clientBuilder = securityContext.outboundClientBuilder() .outboundEnvironment(outboundEnv) .tracingSpan(tracing.findParent().orElse(null)) - .tracingSpan(tracing.findParentSpan().orElse(null)) .outboundEndpointConfig(outboundEp); explicityProvider.ifPresent(clientBuilder::explicitProvider); diff --git a/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/ClientSecurityFeature.java b/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/ClientSecurityFeature.java deleted file mode 100644 index e52421fb4d8..00000000000 --- a/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/ClientSecurityFeature.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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.security.integration.jersey; - -import javax.ws.rs.RuntimeType; -import javax.ws.rs.core.Feature; -import javax.ws.rs.core.FeatureContext; - -/** - * Integration of Security module with Jersey clients. - * If you want to use this class, please inject it as a context and then - * register it on your {@link javax.ws.rs.client.Client}. - * - * @deprecated replaced with {@code io.helidon.security.integration.jersey.client.ClientSecurity} for constants - * the feature is no longer needed to configure security - */ -@Deprecated -public final class ClientSecurityFeature implements Feature { - - /** - * Property name for security context. Set this with - * {@link javax.ws.rs.client.Invocation.Builder#property(String, Object)}, obtained - * through {@link javax.ws.rs.client.WebTarget#request()} - * - * @deprecated use {@code ClientSecurity} constants instead - */ - @Deprecated - public static final String PROPERTY_CONTEXT = "io.helidon.security.jersey.SecureClient.context"; - /** - * Property name for outbound security provider name. Set this with - * {@link javax.ws.rs.client.Invocation.Builder#property(String, Object)}, - * obtained - * through {@link javax.ws.rs.client.WebTarget#request()} - * - * @deprecated use {@code ClientSecurity} constants instead - */ - public static final String PROPERTY_PROVIDER = "io.helidon.security.jersey.SecureClient.explicitProvider"; - - /** - * Create a new security feature. Security context must be provided through for the security context. - * This is a constructor to be used for clients that are not invoked within Jersey server - * context. - */ - public ClientSecurityFeature() { - } - - @Override - public boolean configure(FeatureContext context) { - RuntimeType runtimeType = context.getConfiguration().getRuntimeType(); - - //register client - if (runtimeType == RuntimeType.CLIENT) { - context.register(new ClientSecurityFilter()); - } else { - throw new IllegalStateException( - "ClientSecurityFeature is only available for client side Jersey. For servers, please use SecurityFeature"); - } - - return true; - } -} diff --git a/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/ClientSecurityFilter.java b/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/ClientSecurityFilter.java deleted file mode 100644 index 5eff1327ac7..00000000000 --- a/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/ClientSecurityFilter.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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.security.integration.jersey; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.ws.rs.ConstrainedTo; -import javax.ws.rs.RuntimeType; -import javax.ws.rs.client.ClientRequestContext; -import javax.ws.rs.client.ClientRequestFilter; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.Provider; - -import io.helidon.common.context.Contexts; -import io.helidon.security.EndpointConfig; -import io.helidon.security.OutboundSecurityClientBuilder; -import io.helidon.security.OutboundSecurityResponse; -import io.helidon.security.SecurityContext; -import io.helidon.security.SecurityEnvironment; -import io.helidon.security.SecurityResponse; -import io.helidon.security.integration.common.OutboundTracing; -import io.helidon.security.integration.common.SecurityTracing; - -/** - * JAX-RS client filter propagating current context principal. - *

- * Only works as part of integration with Security component. - * This class is public to allow unit testing from providers (without invoking an HTTP request) - * - * @deprecated This class should not be used directly anyway, yet if you needed it, it is available in - * the new {@code helidon-security-integration-jersey-client} module. - */ -@Provider -@ConstrainedTo(RuntimeType.CLIENT) -@Deprecated -public class ClientSecurityFilter implements ClientRequestFilter { - - private static final Logger LOGGER = Logger.getLogger(ClientSecurityFilter.class.getName()); - - /** - * Create an instance of this filter (used by Jersey or for unit tests, do not use explicitly in your production code). - */ - public ClientSecurityFilter() { - } - - @Override - public void filter(ClientRequestContext requestContext) { - try { - doFilter(requestContext); - } catch (Throwable e) { - //If I do not log the exception here, it would be silently consumed and a 500 response provided to caller - LOGGER.log(Level.WARNING, "Failed to process client filter.", e); - throw e; - } - } - - private void doFilter(ClientRequestContext requestContext) { - //Try to have a look for @AuthenticatedClient annotation on client (if constructed as such) and use explicit provider - // from there - - // first try to find the context on request configuration - findContext(requestContext) - .or(() -> Contexts.context().flatMap(ctx -> ctx.get(SecurityContext.class))) - .ifPresentOrElse(securityContext -> outboundSecurity(requestContext, securityContext), - () -> LOGGER.finest("Security not propagated, as security context is not available " - + "neither in context, nor as the property \"" - + ClientSecurityFeature.PROPERTY_CONTEXT + "\" on request")); - } - - private void outboundSecurity(ClientRequestContext requestContext, SecurityContext securityContext) { - OutboundTracing tracing = SecurityTracing.get().outboundTracing(); - - String explicitProvider = (String) requestContext.getProperty(ClientSecurityFeature.PROPERTY_PROVIDER); - - try { - SecurityEnvironment.Builder outboundEnv = securityContext.env().derive(); - outboundEnv.method(requestContext.getMethod()) - .path(requestContext.getUri().getPath()) - .targetUri(requestContext.getUri()) - .headers(requestContext.getStringHeaders()); - - EndpointConfig.Builder outboundEp = securityContext.endpointConfig().derive(); - - for (String name : requestContext.getConfiguration().getPropertyNames()) { - outboundEp.addAtribute(name, requestContext.getConfiguration().getProperty(name)); - } - - for (String name : requestContext.getPropertyNames()) { - outboundEp.addAtribute(name, requestContext.getProperty(name)); - } - - OutboundSecurityClientBuilder clientBuilder = securityContext.outboundClientBuilder() - .outboundEnvironment(outboundEnv) - .tracingSpan(tracing.findParent().orElse(null)) - .tracingSpan(tracing.findParentSpan().orElse(null)) - .outboundEndpointConfig(outboundEp) - .explicitProvider(explicitProvider); - - OutboundSecurityResponse providerResponse = clientBuilder.buildAndGet(); - SecurityResponse.SecurityStatus status = providerResponse.status(); - tracing.logStatus(status); - switch (status) { - case FAILURE: - case FAILURE_FINISH: - providerResponse.throwable() - .ifPresentOrElse(tracing::error, - () -> tracing.error(providerResponse.description().orElse("Failed"))); - - break; - case ABSTAIN: - case SUCCESS: - case SUCCESS_FINISH: - default: - break; - } - // TODO check response status - maybe entity was updated? - // see MIC-6785; - - Map> newHeaders = providerResponse.requestHeaders(); - - LOGGER.finest(() -> "Client filter header(s). SIZE: " + newHeaders.size()); - - MultivaluedMap hdrs = requestContext.getHeaders(); - for (Map.Entry> entry : newHeaders.entrySet()) { - LOGGER.finest(() -> " + Header: " + entry.getKey() + ": " + entry.getValue()); - - //replace existing - hdrs.remove(entry.getKey()); - for (String value : entry.getValue()) { - hdrs.add(entry.getKey(), value); - } - } - tracing.finish(); - } catch (Exception e) { - tracing.error(e); - throw e; - } - } - - private Optional findContext(ClientRequestContext requestContext) { - Object value = requestContext.getProperty(ClientSecurityFeature.PROPERTY_CONTEXT); - if (null == value) { - value = requestContext.getConfiguration().getProperty(ClientSecurityFeature.PROPERTY_CONTEXT); - } - return Optional.ofNullable((SecurityContext) value); - } -} diff --git a/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/InputStreamPublisher.java b/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/InputStreamPublisher.java deleted file mode 100644 index 3d83b52af83..00000000000 --- a/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/InputStreamPublisher.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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.security.integration.jersey; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Flow; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Publisher that reads data from an input stream and publishes them as {@link ByteBuffer} events. - */ -@SuppressWarnings("Duplicates") -class InputStreamPublisher implements Flow.Publisher { - private final InputStream inputStream; - private final byte[] buffer; - - private final SingleSubscriberHolder subscriber = new SingleSubscriberHolder<>(); - - private final RequestedCounter requested = new RequestedCounter(); - private final AtomicBoolean publishing = new AtomicBoolean(false); - - /** - * Create new input stream publisher that reads data from a supplied input stream and publishes them a single subscriber. - *

- * Note that this implementation does not rely on any asynchronous processing and its business logic is always invoked - * on the subscriber thread (as part of {@link #subscribe(Flow.Subscriber)} and {@link Flow.Subscription#request(long)} - * method calls). - * - * @param inputStream underlying input stream to be used to read the data tu be published as events. - * @param bufferSize maximum published event data buffer size. - */ - InputStreamPublisher(InputStream inputStream, int bufferSize) { - this.inputStream = inputStream; - this.buffer = new byte[bufferSize]; - } - - @Override - public void subscribe(Flow.Subscriber subscriberParam) { - if (subscriber.register(subscriberParam)) { - publishing.set(true); // prevent onNext from inside of onSubscribe - - try { - subscriberParam.onSubscribe(new Flow.Subscription() { - @Override - public void request(long n) { - requested.increment(n, t -> tryComplete(t)); - tryPublish(); - } - - @Override - public void cancel() { - } - }); - } finally { - publishing.set(false); - } - - tryPublish(); // give onNext a chance in case request has been invoked in onSubscribe - } - } - - private void tryPublish() { - while (!subscriber.isClosed() && (requested.get() > 0) && publishing.compareAndSet(false, true)) { - try { - final Flow.Subscriber sub = this.subscriber.get(); // blocking retrieval - - while (!subscriber.isClosed() && requested.tryDecrement()) { - int len = inputStream.read(buffer); - if (len >= 0) { - sub.onNext(ByteBuffer.wrap(buffer, 0, len)); - } else { - tryComplete(); - } - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - tryComplete(e); - } catch (IOException | ExecutionException e) { - tryComplete(e); - } finally { - publishing.set(false); // give a chance to some other thread to publish - } - } - } - - private void tryComplete() { - subscriber.close(Flow.Subscriber::onComplete); - } - - private void tryComplete(Throwable t) { - subscriber.close(sub -> sub.onError(t)); - } -} diff --git a/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/SecureClient.java b/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/SecureClient.java deleted file mode 100644 index f30ff147ad6..00000000000 --- a/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/SecureClient.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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.security.integration.jersey; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.glassfish.jersey.client.ClientConfig; -import org.glassfish.jersey.server.ClientBinding; - -/** - * Annotation to inject clients that have security feature configured. - * Just send security context as request parameter using {@link ClientSecurityFeature#PROPERTY_CONTEXT} and - * security will be handled for outgoing request(s) on this client. - * - *

- * @SecureClient
- * @Uri("http://service-name:8787/base_path")
- * private WebTarget target;
- *
- * @GET
- * public Response getIt(@Context SecurityContext context) {
- *  return target.request()
- *      .property(SecureClient.PROPERTY_CONTEXT, context)
- *      .get();
- * }
- *
- *
- * 
- * - * @deprecated Use the new module {@code helidon-security-integration-jersey-client} that adds security support without coding - */ -@ClientBinding(configClass = SecureClient.SecureClientConfig.class, inheritServerProviders = false) -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD, ElementType.PARAMETER}) -@Deprecated -public @interface SecureClient { - /** - * Configuration class for client security. - */ - class SecureClientConfig extends ClientConfig { - @SuppressWarnings("checkstyle:RedundantModifier") // public modifier required by Jersey - public SecureClientConfig() { - this.register(new ClientSecurityFeature()); - } - } -} diff --git a/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/SecurityFilterCommon.java b/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/SecurityFilterCommon.java index 1d73d932bd6..f5550f599a5 100644 --- a/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/SecurityFilterCommon.java +++ b/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/SecurityFilterCommon.java @@ -139,9 +139,7 @@ protected void authenticate(SecurityFilter.FilterContext context, SecurityContex SecurityClientBuilder clientBuilder = securityContext .atnClientBuilder() .optional(methodSecurity.authenticationOptional()) - .tracingSpan(atnTracing.findParent().orElse(null)) - // backward compatibility - remove in 2.0 - .tracingSpan(atnTracing.findParentSpan().orElse(null)); + .tracingSpan(atnTracing.findParent().orElse(null)); clientBuilder.explicitProvider(methodSecurity.getAuthenticator()); processAuthentication(context, clientBuilder, methodSecurity, atnTracing); @@ -254,8 +252,6 @@ protected void authorize(FilterContext context, // access if (context.getMethodSecurity().requiresAuthorization()) { SecurityClientBuilder clientBuilder = securityContext.atzClientBuilder() - // TODO remove in 2.0 - backward compatibility until then - .tracingSpan(atzTracing.findParentSpan().orElse(null)) .tracingSpan(atzTracing.findParent().orElse(null)) .explicitProvider(context.getMethodSecurity().getAuthorizer()); diff --git a/security/integration/jersey/src/test/java/io/helidon/security/integration/jersey/InputStreamPublisherTest.java b/security/integration/jersey/src/test/java/io/helidon/security/integration/jersey/InputStreamPublisherTest.java index 982fd62e5b0..c40d73daebd 100644 --- a/security/integration/jersey/src/test/java/io/helidon/security/integration/jersey/InputStreamPublisherTest.java +++ b/security/integration/jersey/src/test/java/io/helidon/security/integration/jersey/InputStreamPublisherTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,22 +25,25 @@ import java.util.concurrent.Flow; import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.Test; +import io.helidon.common.reactive.IoMulti; +import io.helidon.common.reactive.Multi; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import org.junit.jupiter.api.Test; + /** - * Unit test for {@link InputStreamPublisher}. + * Unit test for {@code InputStreamPublisher}'s replacement. */ public class InputStreamPublisherTest { @Test public void testSingle() throws InterruptedException { String teststring = "My text to publish with publisher"; - InputStreamPublisher p = - new InputStreamPublisher( - new ByteArrayInputStream(teststring.getBytes(StandardCharsets.UTF_8)), - 1024); + + Multi p = IoMulti.builderInputStream(new ByteArrayInputStream(teststring.getBytes(StandardCharsets.UTF_8))) + .byteBufferSize(1024) + .build(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); CountDownLatch cdl = new CountDownLatch(1); @@ -83,10 +86,9 @@ public void onComplete() { @Test public void testMultiple() throws InterruptedException { String teststring = "My text to publish with publisher"; - InputStreamPublisher p = - new InputStreamPublisher( - new ByteArrayInputStream(teststring.getBytes(StandardCharsets.UTF_8)), - 1); + Multi p = IoMulti.builderInputStream(new ByteArrayInputStream(teststring.getBytes(StandardCharsets.UTF_8))) + .byteBufferSize(1) + .build(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); CountDownLatch cdl = new CountDownLatch(1); @@ -136,10 +138,9 @@ public void testVeryLong() throws IOException, InterruptedException { } teststring = expectedResult.toString(); - InputStreamPublisher p = - new InputStreamPublisher( - new ByteArrayInputStream(teststring.getBytes(StandardCharsets.UTF_8)), - 2); + Multi p = IoMulti.builderInputStream(new ByteArrayInputStream(teststring.getBytes(StandardCharsets.UTF_8))) + .byteBufferSize(2) + .build(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); CountDownLatch cdl = new CountDownLatch(1); diff --git a/security/integration/jersey/src/test/java/io/helidon/security/integration/jersey/TestResource1.java b/security/integration/jersey/src/test/java/io/helidon/security/integration/jersey/TestResource1.java index 101ea55ab88..30d3f11cb78 100644 --- a/security/integration/jersey/src/test/java/io/helidon/security/integration/jersey/TestResource1.java +++ b/security/integration/jersey/src/test/java/io/helidon/security/integration/jersey/TestResource1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,6 @@ */ @Path("/test1") public class TestResource1 { - @SecureClient @Uri("http://localhost:9998/test2") private WebTarget target; @@ -44,7 +43,6 @@ public class TestResource1 { @Produces(MediaType.APPLICATION_JSON) public TransferObject getIt(@Context SecurityContext context) { TransferObject fromTest2 = target.request() - .property(ClientSecurityFeature.PROPERTY_CONTEXT, context) .get(TransferObject.class); // we expect this NOT to be a proxy diff --git a/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java b/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java index e83b36050bb..517778ceb86 100644 --- a/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java +++ b/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java @@ -56,7 +56,6 @@ import io.helidon.webserver.ServerRequest; import io.helidon.webserver.ServerResponse; -import io.opentracing.Span; import io.opentracing.SpanContext; import static io.helidon.security.AuditEvent.AuditParam.plain; @@ -442,8 +441,7 @@ private CompletionStage processAuthentication(ServerResponse res, SecurityClientBuilder clientBuilder = securityContext.atnClientBuilder(); configureSecurityRequest(clientBuilder, - atnTracing.findParent().orElse(null), - atnTracing.findParentSpan().orElse(null)); + atnTracing.findParent().orElse(null)); clientBuilder.explicitProvider(explicitAuthenticator.orElse(null)).submit().thenAccept(response -> { switch (response.status()) { @@ -556,12 +554,10 @@ private void abortRequest(ServerResponse res, } private void configureSecurityRequest(SecurityRequestBuilder> request, - SpanContext parentSpanContext, - Span parentSpan) { + SpanContext parentSpanContext) { request.optional(authenticationOptional.orElse(false)) - .tracingSpan(parentSpanContext) - .tracingSpan(parentSpan); + .tracingSpan(parentSpanContext); } @SuppressWarnings("ThrowableNotThrown") @@ -603,8 +599,7 @@ private CompletionStage processAuthorization(ServerRequest req, client = context.atzClientBuilder(); configureSecurityRequest(client, - atzTracing.findParent().orElse(null), - atzTracing.findParentSpan().orElse(null)); + atzTracing.findParent().orElse(null)); client.explicitProvider(explicitAuthorizer.orElse(null)).submit().thenAccept(response -> { atzTracing.logStatus(response.status()); diff --git a/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/WebSecurity.java b/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/WebSecurity.java index f04912e2f76..1a23de1c932 100644 --- a/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/WebSecurity.java +++ b/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/WebSecurity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -351,11 +351,14 @@ private void registerContext(ServerRequest req, ServerResponse res) { EndpointConfig ec = EndpointConfig.builder() .build(); - SecurityContext context = security.contextBuilder(String.valueOf(SECURITY_COUNTER.incrementAndGet())) - .tracingSpan(req.spanContext()) + SecurityContext.Builder contextBuilder = security.contextBuilder(String.valueOf(SECURITY_COUNTER.incrementAndGet())) .env(env) - .endpointConfig(ec) - .build(); + .endpointConfig(ec); + + // only register if exists + req.spanContext().ifPresent(contextBuilder::tracingSpan); + + SecurityContext context = contextBuilder.build(); req.context().register(context); req.context().register(defaultHandler); diff --git a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityBuilderGateDefaultsTest.java b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityBuilderGateDefaultsTest.java index 4f921167081..6925f4604d7 100644 --- a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityBuilderGateDefaultsTest.java +++ b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityBuilderGateDefaultsTest.java @@ -22,7 +22,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import io.helidon.common.context.Context; import io.helidon.common.http.Http; import io.helidon.common.http.MediaType; import io.helidon.config.Config; @@ -33,7 +32,6 @@ import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientResponse; import io.helidon.webclient.security.WebClientSecurity; -import io.helidon.webclient.security.WebClientSecurityProvider; import io.helidon.webserver.Routing; import io.helidon.webserver.WebServer; @@ -64,7 +62,7 @@ public static void setupClients() { .build(); securitySetup = WebClient.builder() - .register(WebClientSecurity.create(clientSecurity)) + .addService(WebClientSecurity.create(clientSecurity)) .build(); diff --git a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityTests.java b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityTests.java index 5e0522f914f..b59a9963ba7 100644 --- a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityTests.java +++ b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityTests.java @@ -19,11 +19,9 @@ import java.util.Set; import java.util.concurrent.ExecutionException; -import io.helidon.common.context.Context; import io.helidon.common.http.Http; import io.helidon.security.AuditEvent; import io.helidon.security.Security; -import io.helidon.security.SecurityContext; import io.helidon.security.providers.httpauth.HttpBasicAuthProvider; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientResponse; @@ -61,7 +59,7 @@ static void buildClients() { .build(); securitySetup = WebClient.builder() - .register(WebClientSecurity.create(security)) + .addService(WebClientSecurity.create(security)) .build(); webClient = WebClient.create(); diff --git a/security/providers/abac/src/test/java/io/helidon/security/providers/abac/AbacProviderTest.java b/security/providers/abac/src/test/java/io/helidon/security/providers/abac/AbacProviderTest.java index ada6ca88b27..3f90fd08343 100644 --- a/security/providers/abac/src/test/java/io/helidon/security/providers/abac/AbacProviderTest.java +++ b/security/providers/abac/src/test/java/io/helidon/security/providers/abac/AbacProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import io.helidon.security.AuthorizationResponse; import io.helidon.security.EndpointConfig; import io.helidon.security.ProviderRequest; +import io.helidon.security.SecurityLevel; import io.helidon.security.SecurityResponse; import org.junit.jupiter.api.Test; @@ -47,9 +48,12 @@ public void testMissingValidator() { Attrib1 attrib = Mockito.mock(Attrib1.class); doReturn(Attrib1.class).when(attrib).annotationType(); + SecurityLevel level = SecurityLevel.create("mock") + .withClassAnnotations(Map.of(Attrib1.class, List.of(attrib))) + .build(); + EndpointConfig ec = EndpointConfig.builder() - .annotations(EndpointConfig.AnnotationScope.CLASS, - Map.of(Attrib1.class, List.of(attrib))) + .securityLevels(List.of(level)) .build(); ProviderRequest request = Mockito.mock(ProviderRequest.class); @@ -72,9 +76,12 @@ public void testMissingRoleValidator() { RolesAllowed attrib = Mockito.mock(RolesAllowed.class); doReturn(RolesAllowed.class).when(attrib).annotationType(); + SecurityLevel level = SecurityLevel.create("mock") + .withClassAnnotations(Map.of(RolesAllowed.class, List.of(attrib))) + .build(); + EndpointConfig ec = EndpointConfig.builder() - .annotations(EndpointConfig.AnnotationScope.CLASS, - Map.of(RolesAllowed.class, List.of(attrib))) + .securityLevels(List.of(level)) .build(); ProviderRequest request = Mockito.mock(ProviderRequest.class); @@ -98,9 +105,12 @@ public void testExistingValidatorFail() { when(attrib.value()).thenReturn(false); doReturn(Attrib1.class).when(attrib).annotationType(); + SecurityLevel level = SecurityLevel.create("mock") + .withClassAnnotations(Map.of(Attrib1.class, List.of(attrib))) + .build(); + EndpointConfig ec = EndpointConfig.builder() - .annotations(EndpointConfig.AnnotationScope.CLASS, - Map.of(Attrib1.class, List.of(attrib))) + .securityLevels(List.of(level)) .build(); ProviderRequest request = Mockito.mock(ProviderRequest.class); @@ -124,8 +134,12 @@ public void testExistingValidatorSucceed() { when(attrib.value()).thenReturn(true); doReturn(Attrib1.class).when(attrib).annotationType(); + SecurityLevel level = SecurityLevel.create("mock") + .withClassAnnotations(Map.of(Attrib1.class, List.of(attrib))) + .build(); + EndpointConfig ec = EndpointConfig.builder() - .annotations(EndpointConfig.AnnotationScope.CLASS, Map.of(Attrib1.class, List.of(attrib))) + .securityLevels(List.of(level)) .build(); ProviderRequest request = Mockito.mock(ProviderRequest.class); diff --git a/security/providers/http-auth/src/main/java/io/helidon/security/providers/httpauth/ConfigUserStore.java b/security/providers/http-auth/src/main/java/io/helidon/security/providers/httpauth/ConfigUserStore.java index 82c133252fd..8d73686c114 100644 --- a/security/providers/http-auth/src/main/java/io/helidon/security/providers/httpauth/ConfigUserStore.java +++ b/security/providers/http-auth/src/main/java/io/helidon/security/providers/httpauth/ConfigUserStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -85,14 +85,6 @@ static ConfigUser create(Config config) { return cu; } - /** - * @deprecated this is needed (for now) for digest authentication. - */ - @Deprecated - char[] password() { - return password; - } - @Override public String login() { return login; @@ -110,7 +102,7 @@ public Set roles() { @Override public Optional digestHa1(String realm, HttpDigest.Algorithm algorithm) { - return Optional.of(DigestToken.ha1(algorithm, realm, login(), password())); + return Optional.of(DigestToken.ha1(algorithm, realm, login(), password)); } @Override diff --git a/security/providers/http-auth/src/main/java/io/helidon/security/providers/httpauth/UserStore.java b/security/providers/http-auth/src/main/java/io/helidon/security/providers/httpauth/UserStore.java deleted file mode 100644 index 0ce3e095b70..00000000000 --- a/security/providers/http-auth/src/main/java/io/helidon/security/providers/httpauth/UserStore.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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.security.providers.httpauth; - -import java.util.Arrays; -import java.util.Optional; - -/** - * Store of users for resolving httpauth and digest authentication. - * - * @deprecated This store is designed for POC - e.g. no need for better security. You can use - * {@link io.helidon.security.providers.httpauth.SecureUserStore} instead. - */ -@FunctionalInterface -@Deprecated -public interface UserStore extends SecureUserStore { - /** - * Representation of a single user. - */ - interface User extends SecureUserStore.User { - /** - * Get password of the user. - * The password must be provided in clear text, as we may need to create a digest based on the password - * and other (variable) values for digest authentication. - * - * @return password - */ - char[] password(); - - @Override - default boolean isPasswordValid(char[] password) { - return Arrays.equals(password, password()); - } - - @Override - default Optional digestHa1(String realm, HttpDigest.Algorithm algorithm) { - return Optional.of(DigestToken.ha1(algorithm, realm, login(), password())); - } - } -} diff --git a/security/providers/http-auth/src/test/java/io/helidon/security/providers/httpauth/HttpAuthProviderBuilderLegacyUserStoreTest.java b/security/providers/http-auth/src/test/java/io/helidon/security/providers/httpauth/HttpAuthProviderBuilderLegacyUserStoreTest.java deleted file mode 100644 index cc85fdb1b54..00000000000 --- a/security/providers/http-auth/src/test/java/io/helidon/security/providers/httpauth/HttpAuthProviderBuilderLegacyUserStoreTest.java +++ /dev/null @@ -1,545 +0,0 @@ -/* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. - * - * 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.security.providers.httpauth; - -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Base64; -import java.util.Optional; -import java.util.Random; -import java.util.Set; - -import io.helidon.common.Builder; -import io.helidon.security.AuthenticationResponse; -import io.helidon.security.Principal; -import io.helidon.security.Security; -import io.helidon.security.SecurityContext; -import io.helidon.security.SecurityResponse; -import io.helidon.security.Subject; -import io.helidon.security.spi.AuthenticationProvider; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * Unit test for {@link io.helidon.security.providers.httpauth.HttpBasicAuthProvider} and - * {@link io.helidon.security.providers.httpauth.HttpDigestAuthProvider}. - * Todo remove this once UserStore is removed. - */ -public class HttpAuthProviderBuilderLegacyUserStoreTest { - private static final String QUOTE = "\""; - private static Security security; - private volatile static int counter = 0; - private final Random random = new Random(); - private SecurityContext context; - - @BeforeAll - public static void initClass() { - UserStore us = userStore(); - - security = Security.builder() - .addAuthenticationProvider(basicAuthProvider(us), "basic") - .addAuthenticationProvider(digestAuthProvider(false, us), "digest") - .addAuthenticationProvider(digestAuthProvider(true, us), "digest_old") - .build(); - } - - private static UserStore userStore() { - return login -> { - if (login.equals("jack")) { - return Optional.of(new UserStore.User() { - @Override - public String login() { - return "jack"; - } - - @Override - public char[] password() { - return "jackIsGreat".toCharArray(); - } - - @Override - public Set roles() { - return Set.of("user", "admin"); - } - }); - } - if (login.equals("jill")) { - return Optional.of(new UserStore.User() { - @Override - public String login() { - return "jill"; - } - - @Override - public char[] password() { - return "password".toCharArray(); - } - - @Override - public Set roles() { - return Set.of("user"); - } - }); - } - - return Optional.empty(); - }; - } - - private static Builder basicAuthProvider(UserStore us) { - return HttpBasicAuthProvider.builder() - .realm("mic") - .userStore(us); - } - - private static Builder digestAuthProvider(boolean old, UserStore us) { - HttpDigestAuthProvider.Builder builder = HttpDigestAuthProvider.builder() - .realm("mic") - .digestServerSecret("pwd".toCharArray()) - .userStore(us); - - if (old) { - builder.noDigestQop(); - } - - return builder; - } - - @BeforeEach - public void init() { - context = security.contextBuilder(String.valueOf(counter++)) - .build(); - } - - @Test - public void basicTestFail() { - AuthenticationResponse response = context.atnClientBuilder().buildAndGet(); - - assertThat(response.status().isSuccess(), is(false)); - assertThat(response.statusCode().orElse(200), is(401)); - String authHeader = response.responseHeaders().get(HttpBasicAuthProvider.HEADER_AUTHENTICATION_REQUIRED).get(0); - assertThat(authHeader, notNullValue()); - assertThat(authHeader.toLowerCase(), is("basic realm=\"mic\"")); - } - - @Test - public void basicTestJack() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, buildBasic("jack", "jackIsGreat")); - - AuthenticationResponse response = context.authenticate(); - - assertThat(response.status(), is(SecurityResponse.SecurityStatus.SUCCESS)); - assertThat(response.statusCode().orElse(200), is(200)); - - assertThat(context.user().map(sub -> sub.principal().getName()).orElse(null), is("jack")); - assertThat(context.isUserInRole("admin"), is(true)); - assertThat(context.isUserInRole("user"), is(true)); - } - - private void setHeader(SecurityContext context, String name, String value) { - context.env(context.env() - .derive() - .header(name, value) - .build()); - } - - @Test - public void basicTestInvalidUser() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, buildBasic("wrong", "user")); - - AuthenticationResponse response = context.authenticate(); - - assertThat(response.description().orElse(""), is("Invalid username or password")); - assertThat(response.status().isSuccess(), is(false)); - assertThat(response.statusCode().orElse(200), is(401)); - String authHeader = response.responseHeaders().get(HttpBasicAuthProvider.HEADER_AUTHENTICATION_REQUIRED).get(0); - assertThat(authHeader, notNullValue()); - assertThat(authHeader.toLowerCase(), is("basic realm=\"mic\"")); - - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, buildBasic("jack", "invalid_passworrd")); - - response = context.authenticate(); - - assertThat(response.description().orElse(""), is("Invalid username or password")); - assertThat(response.status().isSuccess(), is(false)); - assertThat(response.statusCode().orElse(200), is(401)); - } - - @Test - public void sendInvalidTypeTest() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, "bearer token=\"adfasfaf\""); - - AuthenticationResponse response = context.authenticate(); - - assertThat(response.status(), is(SecurityResponse.SecurityStatus.FAILURE)); - assertThat(response.statusCode().orElse(200), is(401)); - } - - @Test - public void sendInvalidBasicTest() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, "basic wrong_header_value"); - AuthenticationResponse response = context.authenticate(); - - assertThat(response.status(), is(SecurityResponse.SecurityStatus.FAILURE)); - assertThat(response.statusCode().orElse(200), is(401)); - - // not base64 encoded and invalid - setHeader(context, - HttpBasicAuthProvider.HEADER_AUTHENTICATION, - "basic " + Base64.getEncoder().encodeToString("Hello".getBytes())); - - response = context.authenticate(); - - assertThat(response.status(), is(SecurityResponse.SecurityStatus.FAILURE)); - assertThat(response.statusCode().orElse(200), is(401)); - } - - @Test - public void sendDigestNotBasicTest() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, buildDigest(HttpDigest.Qop.AUTH, "jack", "jackIsGreat")); - AuthenticationResponse response = context.authenticate(); - - assertThat(response.status(), is(SecurityResponse.SecurityStatus.FAILURE)); - assertThat(response.statusCode().orElse(200), is(401)); - } - - @Test - public void basicTestJill() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, buildBasic("jill", "password")); - AuthenticationResponse response = context.authenticate(); - - assertThat(response.status(), is(SecurityResponse.SecurityStatus.SUCCESS)); - assertThat(response.statusCode().orElse(200), is(200)); - - assertThat(getUsername(context), is("jill")); - assertThat(context.isUserInRole("admin"), is(false)); - assertThat(context.isUserInRole("user"), is(true)); - } - - @Test - public void digestTest401() { - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest") - .buildAndGet(); - - assertThat(response.status().isSuccess(), is(false)); - assertThat(response.statusCode().orElse(200), is(401)); - String authHeader = response.responseHeaders().get(HttpBasicAuthProvider.HEADER_AUTHENTICATION_REQUIRED).get(0); - assertThat(authHeader, notNullValue()); - assertThat(authHeader.toLowerCase(), startsWith("digest realm=\"mic\"")); - assertThat(authHeader.toLowerCase(), containsString("qop=")); - } - - @Test - public void digestOldTest401() { - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest_old") - .buildAndGet(); - - assertThat(response.status().isSuccess(), is(false)); - assertThat(response.statusCode().orElse(200), is(401)); - String authHeader = response.responseHeaders().get(HttpBasicAuthProvider.HEADER_AUTHENTICATION_REQUIRED).get(0); - assertThat(authHeader, notNullValue()); - assertThat(authHeader.toLowerCase(), startsWith("digest realm=\"mic\"")); - assertThat(authHeader.toLowerCase(), not(containsString("qop="))); - } - - @Test - public void digestTestJack() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, buildDigest(HttpDigest.Qop.AUTH, "jack", "jackIsGreat")); - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest") - .buildAndGet(); - - assertThat(response.description().orElse("No description"), - response.status(), - is(SecurityResponse.SecurityStatus.SUCCESS)); - assertThat(response.statusCode().orElse(200), is(200)); - - assertThat(getUsername(context), is("jack")); - assertThat(context.isUserInRole("admin"), is(true)); - assertThat(context.isUserInRole("user"), is(true)); - } - - @Test - public void digestTestInvalidUser() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, buildDigest(HttpDigest.Qop.AUTH, "wrong", "user")); - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest") - .buildAndGet(); - - assertThat(response.description().orElse(""), is("Invalid username or password")); - assertThat(response.status().isSuccess(), is(false)); - assertThat(response.statusCode().orElse(200), is(401)); - String authHeader = response.responseHeaders().get(HttpBasicAuthProvider.HEADER_AUTHENTICATION_REQUIRED).get(0); - assertThat(authHeader, notNullValue()); - assertThat(authHeader.toLowerCase(), startsWith("digest realm=\"mic\"")); - - setHeader(context, - HttpBasicAuthProvider.HEADER_AUTHENTICATION, - buildDigest(HttpDigest.Qop.AUTH, "jack", "wrong password")); - response = context.atnClientBuilder() - .explicitProvider("digest") - .buildAndGet(); - - assertThat(response.description().orElse(""), is("Invalid username or password")); - assertThat(response.status().isSuccess(), is(false)); - assertThat(response.statusCode().orElse(200), is(401)); - } - - @Test - public void sendBasicNotDigestTest() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, buildBasic("jack", "jackIsGreat")); - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest") - .buildAndGet(); - - assertThat(response.status(), is(SecurityResponse.SecurityStatus.FAILURE)); - assertThat(response.statusCode().orElse(200), is(401)); - } - - @Test - public void sendInvalidDigestTest() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, "digest wrong_header_value"); - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest") - .buildAndGet(); - - assertThat(response.status(), is(SecurityResponse.SecurityStatus.FAILURE)); - assertThat(response.statusCode().orElse(200), is(401)); - } - - @Test - public void digestTestJill() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, buildDigest(HttpDigest.Qop.AUTH, "jill", "password")); - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest") - .buildAndGet(); - - assertThat(response.description().orElse("No description"), - response.status(), - is(SecurityResponse.SecurityStatus.SUCCESS)); - assertThat(response.statusCode().orElse(200), is(200)); - - assertThat(getUsername(context), is("jill")); - assertThat(context.isUserInRole("admin"), is(false)); - assertThat(context.isUserInRole("user"), is(true)); - } - - private String getUsername(SecurityContext context) { - return context.user().map(Subject::principal).map(Principal::getName).orElse(null); - } - - @Test - public void digestOldTestJack() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, buildDigest(HttpDigest.Qop.NONE, "jack", "jackIsGreat")); - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest_old") - .buildAndGet(); - - assertThat(response.description().orElse("No description"), - response.status(), - is(SecurityResponse.SecurityStatus.SUCCESS)); - assertThat(response.statusCode().orElse(200), is(200)); - - assertThat(getUsername(context), is("jack")); - assertThat(context.isUserInRole("admin"), is(true)); - assertThat(context.isUserInRole("user"), is(true)); - } - - @Test - public void digestOldTestJill() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, buildDigest(HttpDigest.Qop.NONE, "jill", "password")); - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest_old") - .buildAndGet(); - - assertThat(response.description().orElse("No description"), - response.status(), - is(SecurityResponse.SecurityStatus.SUCCESS)); - assertThat(response.statusCode().orElse(200), is(200)); - - assertThat(getUsername(context), is("jill")); - assertThat(context.isUserInRole("admin"), is(false)); - assertThat(context.isUserInRole("user"), is(true)); - } - - @Test - public void digestTestNonceTimeout() { - Instant in = Instant.now().minus(100, ChronoUnit.DAYS); - - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, - buildDigest(HttpDigest.Qop.AUTH, - "jack", - "jackIsGreat", - HttpDigestAuthProvider.nonce(in.toEpochMilli(), random, "pwd".toCharArray()), - "mic")); - - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest") - .buildAndGet(); - - assertThat(response.description().orElse(""), is("Nonce timeout")); - assertThat(response.status(), is(SecurityResponse.SecurityStatus.FAILURE)); - assertThat(response.statusCode().orElse(200), is(401)); - } - - @Test - public void digestTestNonceNotB64() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, - buildDigest(HttpDigest.Qop.AUTH, - "jack", - "jackIsGreat", - "Not a base64 encoded $tring", - "mic")); - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest") - .buildAndGet(); - - assertThat(response.description().orElse(""), is("Nonce must be base64 encoded")); - assertThat(response.status(), is(SecurityResponse.SecurityStatus.FAILURE)); - assertThat(response.statusCode().orElse(200), is(401)); - } - - @Test - public void digestTestNonceTooShort() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, - buildDigest(HttpDigest.Qop.AUTH, - "jack", - "jackIsGreat", - // must be base64 encoded string of less than 17 bytes - "wrongNonce", - "mic")); - - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest") - .buildAndGet(); - assertThat(response.description().orElse(""), is("Invalid nonce length")); - assertThat(response.status(), is(SecurityResponse.SecurityStatus.FAILURE)); - assertThat(response.statusCode().orElse(200), is(401)); - } - - @Test - public void digestTestNonceNotEncrypted() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, - buildDigest(HttpDigest.Qop.AUTH, - "jack", - "jackIsGreat", - Base64.getEncoder() - .encodeToString("4444444444444444444444444444444444444444444444".getBytes()), - "mic")); - - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest") - .buildAndGet(); - assertThat(response.description().orElse(""), is("Invalid nonce value")); - assertThat(response.status(), is(SecurityResponse.SecurityStatus.FAILURE)); - assertThat(response.statusCode().orElse(200), is(401)); - } - - @Test - public void digestTestWrongRealm() { - setHeader(context, HttpBasicAuthProvider.HEADER_AUTHENTICATION, - buildDigest(HttpDigest.Qop.AUTH, - "jack", - "jackIsGreat", - HttpDigestAuthProvider.nonce(System.currentTimeMillis(), random, "pwd".toCharArray()), - "wrongRealm")); - AuthenticationResponse response = context.atnClientBuilder() - .explicitProvider("digest") - .buildAndGet(); - - assertThat(response.description().orElse(""), is("Invalid realm")); - assertThat(response.status(), is(SecurityResponse.SecurityStatus.FAILURE)); - assertThat(response.statusCode().orElse(200), is(401)); - } - - private String buildBasic(String user, String password) { - return "basic " + Base64.getEncoder() - .encodeToString((user + ":" + password).getBytes(StandardCharsets.UTF_8)); - } - - private String buildDigest(HttpDigest.Qop qop, - String user, - String password, - String nonce, - String realm) { - - StringBuilder result = new StringBuilder(100); - - String opaque = "someString"; - String path = "/digest"; - String cnonce = createCnonce(); - String nc = "00000001"; - - DigestToken token = new DigestToken(); - token.setAlgorithm(HttpDigest.Algorithm.MD5); - token.setQop(qop); - token.setUsername(user); - token.setOpaque(opaque); - token.setRealm(realm); - token.setMethod("GET"); - token.setUri(path); - token.setNonce(nonce); - token.setNc(nc); - token.setCnonce(cnonce); - - String response = token.digest(password.toCharArray()); - - result.append("digest "); - result.append("username=").append(QUOTE).append(user).append(QUOTE); - result.append(", realm=").append(QUOTE).append(realm).append(QUOTE); - result.append(", nonce=").append(QUOTE).append(nonce).append(QUOTE); - result.append(", uri=").append(QUOTE).append(path).append(QUOTE); - result.append(", algorithm=MD5"); - result.append(", response=").append(QUOTE).append(response).append(QUOTE); - result.append(", opaque=").append(QUOTE).append(opaque).append(QUOTE); - if (qop != HttpDigest.Qop.NONE) { - result.append(", qop=").append(qop.getQop()); - } - result.append(", nc=").append(nc); - result.append(", cnonce=").append(QUOTE).append(cnonce).append(QUOTE); - - return result.toString(); - } - - private String buildDigest(HttpDigest.Qop qop, String user, String password) { - return buildDigest(qop, - user, - password, - HttpDigestAuthProvider.nonce(System.currentTimeMillis(), random, "pwd".toCharArray()), - "mic"); - } - - private String createCnonce() { - byte[] cnonce = new byte[8]; - random.nextBytes(cnonce); - - return Base64.getEncoder().encodeToString(cnonce); - } - -} diff --git a/security/security/src/main/java/io/helidon/security/EndpointConfig.java b/security/security/src/main/java/io/helidon/security/EndpointConfig.java index 2d44e51649e..c68f184f406 100644 --- a/security/security/src/main/java/io/helidon/security/EndpointConfig.java +++ b/security/security/src/main/java/io/helidon/security/EndpointConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or 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,12 +16,10 @@ package io.helidon.security; -import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -120,42 +118,6 @@ public Optional config(String configKey) { return Optional.ofNullable(configMap.get(configKey)); } - /** - * All custom annotations for scopes defined in parameters, in the same order. - * - * @param scopes scopes the caller is interested in - * @return a map of annotation classes to annotation instances - * @see SecurityProvider#supportedAnnotations() - * @deprecated use iteration over security levels instead - */ - @Deprecated - public Map, List> annotations(AnnotationScope... scopes) { - Map, List> result = new HashMap<>(); - - for (AnnotationScope scope : scopes) { - Map, List> map; - switch (scope) { - case APPLICATION: - map = securityLevels.get(0).getClassLevelAnnotations(); - break; - case CLASS: - map = securityLevels.get(securityLevels.size() - 1).getClassLevelAnnotations(); - break; - case METHOD: - map = securityLevels.get(securityLevels.size() - 1).getMethodLevelAnnotations(); - break; - default: - map = null; - } - if (null != map) { - map.forEach((annotClass, annotList) -> result.computeIfAbsent(annotClass, aClass -> new LinkedList<>()) - .addAll(annotList)); - } - } - - return result; - } - /** * Get all security levels endpoint configuration object registered. * The first level represents {@link AnnotationScope#APPLICATION} level annotations. @@ -167,25 +129,6 @@ public List securityLevels() { return securityLevels; } - /** - * Get all annotations of a specific class declared on any level. - * - * @param annotationClass Class of annotation you want - * @param scopes scopes the caller is interested in - * @param type of annotation wanted - * @return list of annotations in order specified by methodFirst parameter - * @deprecated use iteration over security levels instead - */ - @Deprecated - @SuppressWarnings("unchecked") - public List combineAnnotations(Class annotationClass, AnnotationScope... scopes) { - List result = new LinkedList<>(); - - result.addAll((Collection) annotations(scopes).getOrDefault(annotationClass, List.of())); - - return result; - } - /** * Derive a new endpoint configuration builder based on this instance. * @@ -296,62 +239,6 @@ public Builder configMap(Map configMap) { return this; } - /** - * Add annotations of a specific scope to this request builder. - * Only used by frameworks that use annotations. - * - * @param scope Annotation scope to add annotations for - * @param annotations Collected annotations based on security provider requirements. - * @return updated Builder instance - * @deprecated Use the {@link #securityLevels(List) securityLevels} method. - */ - @Deprecated - public Builder annotations(AnnotationScope scope, - Map, List> annotations) { - // here we must switch from a proxy to actual annotation type - Map, List> newAnnots = new HashMap<>(); - - if (securityLevels.isEmpty()) { - securityLevels.add(SecurityLevel.create("APPLICATION").build()); //Security level of Application - securityLevels.add(SecurityLevel.create("CLASS").build()); // Security level of class and method - } - SecurityLevel securityLevel; - int index; - if (scope == AnnotationScope.APPLICATION) { - index = 0; - } else { - index = securityLevels.size() - 1; - } - securityLevel = securityLevels.get(index); - - annotations.forEach((aClass, list) -> { - if (!list.isEmpty()) { - Annotation annotation = list.get(0); - newAnnots.put(annotation.annotationType(), list); - } - }); - - switch (scope) { - case APPLICATION: - case CLASS: - securityLevels.set(index, - SecurityLevel.create(securityLevel) - .withClassAnnotations(newAnnots) - .build()); - break; - case METHOD: - securityLevels.set(index, - SecurityLevel.create(securityLevel) - .withMethodAnnotations(newAnnots) - .build()); - break; - default: - throw new IllegalStateException("Scope FIELD is not supported here."); - } - - return this; - } - /** * Attributes of this endpoint configuration. * diff --git a/security/security/src/main/java/io/helidon/security/SecurityRequest.java b/security/security/src/main/java/io/helidon/security/SecurityRequest.java index 26b1f86cbbc..31330b5c1eb 100644 --- a/security/security/src/main/java/io/helidon/security/SecurityRequest.java +++ b/security/security/src/main/java/io/helidon/security/SecurityRequest.java @@ -20,7 +20,6 @@ import java.util.Optional; import java.util.function.Supplier; -import io.opentracing.Span; import io.opentracing.SpanContext; /** @@ -38,17 +37,6 @@ default boolean isOptional() { return false; } - /** - * Get the span to trace subsequent requests. - * - * @return Open tracing Span instance (started) of the parent of the current request, never null. - * @see io.opentracing.util.GlobalTracer#get() - * @see io.opentracing.Tracer#buildSpan(String) - * @deprecated use {@link #tracingSpanContext()} instead - */ - @Deprecated - Span tracingSpan(); - /** * Parent span for tracing. There may be no parent defined (such as when tracing is disabled). * diff --git a/security/security/src/main/java/io/helidon/security/SecurityRequestBuilder.java b/security/security/src/main/java/io/helidon/security/SecurityRequestBuilder.java index 12c99a0d394..cfd5ae0c6a9 100644 --- a/security/security/src/main/java/io/helidon/security/SecurityRequestBuilder.java +++ b/security/security/src/main/java/io/helidon/security/SecurityRequestBuilder.java @@ -24,7 +24,6 @@ import io.helidon.security.spi.SecurityProvider; -import io.opentracing.Span; import io.opentracing.SpanContext; /** @@ -38,7 +37,6 @@ public class SecurityRequestBuilder> { private final Map> resources = new HashMap<>(); private String providerName; private boolean isOptional; - private Span tracingSpan; private SpanContext tracingSpanContext; @SuppressWarnings("unchecked") @@ -101,22 +99,6 @@ public T object(String key, Supplier object) { return myInstance; } - /** - * Tracing span to support Open tracing. Provider developer can add additional spans as children of this span - * to trace their progress. - * - * @param span span of current security request (e.g. authentication, authorization or outbound) - * @return updated builder instance - * @see io.opentracing.util.GlobalTracer#get() - * @see io.opentracing.Tracer#buildSpan(String) - * @deprecated to be removed in 2.0, now needed for backward compatibility - */ - @Deprecated - public T tracingSpan(Span span) { - this.tracingSpan = span; - return myInstance; - } - /** * Tracing span to support Open tracing. Provider developer can add additional spans as children of this span * to trace their progress. @@ -168,17 +150,15 @@ String providerName() { return providerName; } - private final class SecurityRequestImpl implements SecurityRequest { + private static final class SecurityRequestImpl implements SecurityRequest { private final String providerName; private final boolean isOptional; - private final Span tracingSpan; private final Optional tracingSpanContext; private final Map> resources = new HashMap<>(); private SecurityRequestImpl(SecurityRequestBuilder builder) { this.providerName = builder.providerName; this.isOptional = builder.isOptional; - this.tracingSpan = builder.tracingSpan; this.tracingSpanContext = Optional.ofNullable(builder.tracingSpanContext); this.resources.putAll(builder.resources); } @@ -188,11 +168,6 @@ public boolean isOptional() { return isOptional; } - @Override - public Span tracingSpan() { - return tracingSpan; - } - @Override public Optional tracingSpanContext() { return tracingSpanContext; @@ -207,7 +182,6 @@ public String toString() { return "SecurityRequestImpl{" + "providerName='" + providerName + '\'' + ", isOptional=" + isOptional - + ", tracingSpan=" + tracingSpan + '}'; } } diff --git a/security/security/src/main/java/io/helidon/security/spi/SecurityProvider.java b/security/security/src/main/java/io/helidon/security/spi/SecurityProvider.java index a8c3bc12d3e..f79543aa144 100644 --- a/security/security/src/main/java/io/helidon/security/spi/SecurityProvider.java +++ b/security/security/src/main/java/io/helidon/security/spi/SecurityProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or 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,8 +36,6 @@ public interface SecurityProvider { * class and resource methods will be collected. * * @return Collection of annotations this provider expects. - * @see EndpointConfig#annotations(EndpointConfig.AnnotationScope...) - * @see EndpointConfig#combineAnnotations(Class, EndpointConfig.AnnotationScope...) */ default Collection> supportedAnnotations() { return Set.of(); diff --git a/tests/apps/bookstore/bookstore-mp/pom.xml b/tests/apps/bookstore/bookstore-mp/pom.xml index 9ddd6dd2aea..27b1a77cb32 100644 --- a/tests/apps/bookstore/bookstore-mp/pom.xml +++ b/tests/apps/bookstore/bookstore-mp/pom.xml @@ -53,16 +53,6 @@ org.glassfish.jersey.media jersey-media-json-binding - - - jakarta.activation - jakarta.activation-api - - - com.sun.activation - jakarta.activation - - io.helidon.tests.apps.bookstore.common diff --git a/tests/apps/bookstore/bookstore-se/pom.xml b/tests/apps/bookstore/bookstore-se/pom.xml index 921e8279c93..a771a28140a 100644 --- a/tests/apps/bookstore/bookstore-se/pom.xml +++ b/tests/apps/bookstore/bookstore-se/pom.xml @@ -55,16 +55,16 @@ helidon-metrics - io.helidon.media.jsonp - helidon-media-jsonp-common + io.helidon.media + helidon-media-jsonp - io.helidon.media.jsonb - helidon-media-jsonb-common + io.helidon.media + helidon-media-jsonb - io.helidon.media.jackson - helidon-media-jackson-common + io.helidon.media + helidon-media-jackson io.helidon.tests.apps.bookstore.common diff --git a/tests/apps/bookstore/bookstore-se/src/main/java/io/helidon/tests/apps/bookstore/se/Main.java b/tests/apps/bookstore/bookstore-se/src/main/java/io/helidon/tests/apps/bookstore/se/Main.java index 1a0c8d204c4..1d81695bb2a 100644 --- a/tests/apps/bookstore/bookstore-se/src/main/java/io/helidon/tests/apps/bookstore/se/Main.java +++ b/tests/apps/bookstore/bookstore-se/src/main/java/io/helidon/tests/apps/bookstore/se/Main.java @@ -24,8 +24,9 @@ import io.helidon.config.Config; import io.helidon.health.HealthSupport; import io.helidon.health.checks.HealthChecks; -import io.helidon.media.jsonb.common.JsonbSupport; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jackson.JacksonSupport; +import io.helidon.media.jsonb.JsonbSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.metrics.MetricsSupport; import io.helidon.webserver.ExperimentalConfiguration; import io.helidon.webserver.Http2Configuration; @@ -120,7 +121,7 @@ private static void configureJsonSupport(WebServer.Builder wsBuilder, Config con wsBuilder.addMediaSupport(JsonbSupport.create()); break; case JACKSON: - wsBuilder.addMediaSupport(io.helidon.media.jackson.common.JacksonSupport.create()); + wsBuilder.addMediaSupport(JacksonSupport.create()); break; default: throw new RuntimeException("Unknown JSON library " + jsonLibrary); diff --git a/tests/apps/bookstore/bookstore-se/src/main/java/module-info.java b/tests/apps/bookstore/bookstore-se/src/main/java/module-info.java index d23b61be452..0badbcb1016 100644 --- a/tests/apps/bookstore/bookstore-se/src/main/java/module-info.java +++ b/tests/apps/bookstore/bookstore-se/src/main/java/module-info.java @@ -27,9 +27,9 @@ requires io.helidon.health; requires io.helidon.health.checks; requires io.helidon.metrics; - requires io.helidon.media.jsonp.common; - requires io.helidon.media.jsonb.common; - requires io.helidon.media.jackson.common; + requires io.helidon.media.jsonp; + requires io.helidon.media.jsonb; + requires io.helidon.media.jackson; requires io.helidon.tests.apps.bookstore.common; exports io.helidon.tests.apps.bookstore.se; diff --git a/tests/functional/bookstore/pom.xml b/tests/functional/bookstore/pom.xml index f6eb5e397ea..76c6472c166 100644 --- a/tests/functional/bookstore/pom.xml +++ b/tests/functional/bookstore/pom.xml @@ -72,8 +72,8 @@ test - io.helidon.media.jsonp - helidon-media-jsonp-server + io.helidon.media + helidon-media-jsonp test 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 09cef32193f..0282ae5ef1f 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 @@ -34,7 +34,7 @@ import io.helidon.common.http.Http; import io.helidon.common.http.MediaType; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientResponse; diff --git a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonProvider.java b/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/ContextFilter.java similarity index 56% rename from media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonProvider.java rename to tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/ContextFilter.java index 8eff3ee9f04..53c83ebe605 100644 --- a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonProvider.java +++ b/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/ContextFilter.java @@ -14,26 +14,21 @@ * limitations under the License. */ -package io.helidon.media.jackson.common; +package io.helidon.tests.functional.context.hello; -import io.helidon.config.Config; -import io.helidon.media.common.MediaSupport; -import io.helidon.media.common.spi.MediaSupportProvider; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.ext.Provider; + +import io.helidon.common.context.Contexts; /** - * Jackson support SPI provider. + * A filter that adds request scoped context record. */ -public class JacksonProvider implements MediaSupportProvider { - - private static final String JACKSON = "jackson"; - - @Override - public MediaSupport create(Config config) { - return JacksonSupport.create(); - } - +@Provider +public class ContextFilter implements ContainerRequestFilter { @Override - public String type() { - return JACKSON; + public void filter(ContainerRequestContext requestContext) { + Contexts.context().ifPresent(ctx -> ctx.register(new MyMessage())); } } diff --git a/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/HelloBean.java b/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/HelloBean.java index 0ebcc5616b2..4423b3ba55c 100644 --- a/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/HelloBean.java +++ b/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/HelloBean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,8 @@ import io.helidon.common.context.Context; import io.helidon.common.context.Contexts; +import io.helidon.metrics.RegistryFactory; -import io.opentracing.SpanContext; import org.eclipse.microprofile.faulttolerance.Asynchronous; import org.eclipse.microprofile.faulttolerance.Timeout; @@ -42,9 +42,12 @@ public class HelloBean { * @return Hello string. */ public String getHello() { - Context context = Contexts.context().get(); + Context context = Contexts.context().orElse(null); Objects.requireNonNull(context); - Objects.requireNonNull(context.get(SpanContext.class).get()); + // application scoped context + Objects.requireNonNull(context.get(RegistryFactory.class).orElse(null)); + // request scoped context + Objects.requireNonNull(context.get(MyMessage.class).orElse(null)); return "Hello World"; } @@ -55,9 +58,12 @@ public String getHello() { */ @Timeout(1000) public String getHelloTimeout() { - Context context = Contexts.context().get(); + Context context = Contexts.context().orElse(null); Objects.requireNonNull(context); - Objects.requireNonNull(context.get(SpanContext.class).get()); + // application scoped context + Objects.requireNonNull(context.get(RegistryFactory.class).orElse(null)); + // request scoped context + Objects.requireNonNull(context.get(MyMessage.class).orElse(null)); return "Hello World"; } @@ -68,9 +74,12 @@ public String getHelloTimeout() { */ @Asynchronous public CompletionStage getHelloAsync() { - Context context = Contexts.context().get(); + Context context = Contexts.context().orElse(null); Objects.requireNonNull(context); - Objects.requireNonNull(context.get(SpanContext.class).get()); + // application scoped context + Objects.requireNonNull(context.get(RegistryFactory.class).orElse(null)); + // request scoped context + Objects.requireNonNull(context.get(MyMessage.class).orElse(null)); return CompletableFuture.completedFuture("Hello World"); } } diff --git a/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/HelloResource.java b/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/HelloResource.java index 00b33991f33..6b0bcedf698 100644 --- a/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/HelloResource.java +++ b/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/HelloResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/MyMessage.java b/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/MyMessage.java new file mode 100644 index 00000000000..577e3d9543c --- /dev/null +++ b/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/MyMessage.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.functional.context.hello; + +/** + * Class to test context. + */ +class MyMessage { + MyMessage() { + } +} diff --git a/tests/functional/context-propagation/src/test/java/io/helidon/tests/functional/context/hello/HelloTest.java b/tests/functional/context-propagation/src/test/java/io/helidon/tests/functional/context/hello/HelloTest.java index 2be96f27572..31bba6c7a94 100644 --- a/tests/functional/context-propagation/src/test/java/io/helidon/tests/functional/context/hello/HelloTest.java +++ b/tests/functional/context-propagation/src/test/java/io/helidon/tests/functional/context/hello/HelloTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,19 +55,19 @@ static void destroyClass() { } @Test - void TestHello() { + void testHello() { WebTarget target = baseTarget.path("/hello"); assertOk(target.request().get(), "Hello World"); } @Test - void TestHelloTimeout() { + void testHelloTimeout() { WebTarget target = baseTarget.path("/helloTimeout"); assertOk(target.request().get(), "Hello World"); } @Test - void TestHelloAsync() { + void testHelloAsync() { WebTarget target = baseTarget.path("/helloAsync"); assertOk(target.request().get(), "Hello World"); } diff --git a/tests/functional/mp-synthetic-app/src/main/resources/logging.properties b/tests/functional/mp-synthetic-app/src/main/resources/logging.properties index de3dd87947d..b2f1ba7d8fe 100644 --- a/tests/functional/mp-synthetic-app/src/main/resources/logging.properties +++ b/tests/functional/mp-synthetic-app/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ io.helidon.level=INFO # It replaces "!thread!" with the current thread name #java.util.logging.ConsoleHandler.level=ALL io.helidon.common.HelidonConsoleHandler.level=ALL -#java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter 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 AUDIT.level=FINEST io.helidon.webserver.level=WARNING diff --git a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/tests/interceptor/InterceptorIT.java b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/tests/interceptor/InterceptorIT.java index d2e1a103f35..92a3b92511e 100644 --- a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/tests/interceptor/InterceptorIT.java +++ b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/tests/interceptor/InterceptorIT.java @@ -15,14 +15,12 @@ */ package io.helidon.tests.integration.dbclient.common.tests.interceptor; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; - import io.helidon.common.reactive.Multi; +import io.helidon.common.reactive.Single; import io.helidon.config.Config; import io.helidon.dbclient.DbClient; -import io.helidon.dbclient.DbInterceptor; -import io.helidon.dbclient.DbInterceptorContext; +import io.helidon.dbclient.DbClientService; +import io.helidon.dbclient.DbClientServiceContext; import io.helidon.dbclient.DbRow; import io.helidon.tests.integration.dbclient.common.AbstractIT; @@ -33,40 +31,40 @@ import static org.hamcrest.Matchers.equalTo; /** - * Verify interceptors handling. + * Verify services handling. */ public class InterceptorIT { - private static final class TestInterceptor implements DbInterceptor { + private static final class TestClientService implements DbClientService { private boolean called; - private DbInterceptorContext context; + private DbClientServiceContext context; - private TestInterceptor() { + private TestClientService() { this.called = false; this.context = null; } @Override - public CompletionStage statement(DbInterceptorContext context) { + public Single statement(DbClientServiceContext context) { this.called = true; this.context = context; - return CompletableFuture.completedFuture(context); + return Single.just(context); } private boolean called() { return called; } - private DbInterceptorContext getContext() { + private DbClientServiceContext getContext() { return context; } } - private static DbClient initDbClient(TestInterceptor interceptor) { + private static DbClient initDbClient(TestClientService interceptor) { Config dbConfig = AbstractIT.CONFIG.get("db"); - return DbClient.builder(dbConfig).addInterceptor(interceptor).build(); + return DbClient.builder(dbConfig).addService(interceptor).build(); } /** @@ -75,7 +73,7 @@ private static DbClient initDbClient(TestInterceptor interceptor) { */ @Test public void testStatementInterceptor() { - TestInterceptor interceptor = new TestInterceptor(); + TestClientService interceptor = new TestClientService(); DbClient dbClient = initDbClient(interceptor); Multi rows = dbClient.execute(exec -> exec .createNamedQuery("select-pokemon-named-arg") diff --git a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/tests/mapping/MapperIT.java b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/tests/mapping/MapperIT.java index 50d136e64d6..991fedc73f5 100644 --- a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/tests/mapping/MapperIT.java +++ b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/tests/mapping/MapperIT.java @@ -48,7 +48,7 @@ private static void addPokemon(Pokemon pokemon) throws ExecutionException, Inter POKEMONS.put(pokemon.getId(), pokemon); Long result = DB_CLIENT.execute(exec -> exec .namedInsert("insert-pokemon", pokemon.getId(), pokemon.getName()) - ).toCompletableFuture().get(); + ).await(); verifyInsertPokemon(result, pokemon); } @@ -86,7 +86,7 @@ public void testInsertWithOrderMapping() throws ExecutionException, InterruptedE .createNamedInsert("insert-pokemon-order-arg-rev") .indexedParam(pokemon) .execute() - ).toCompletableFuture().get(); + ).await(); verifyInsertPokemon(result, pokemon); } @@ -103,7 +103,7 @@ public void testInsertWithNamedMapping() throws ExecutionException, InterruptedE .createNamedInsert("insert-pokemon-named-arg") .namedParam(pokemon) .execute() - ).toCompletableFuture().get(); + ).await(); verifyInsertPokemon(result, pokemon); } @@ -120,7 +120,7 @@ public void testUpdateWithOrderMapping() throws ExecutionException, InterruptedE .createNamedUpdate("update-pokemon-order-arg") .indexedParam(pokemon) .execute() - ).toCompletableFuture().get(); + ).await(); verifyUpdatePokemon(result, pokemon); } @@ -137,7 +137,7 @@ public void testUpdateWithNamedMapping() throws ExecutionException, InterruptedE .createNamedUpdate("update-pokemon-named-arg") .namedParam(pokemon) .execute() - ).toCompletableFuture().get(); + ).await(); verifyUpdatePokemon(result, pokemon); } @@ -154,7 +154,7 @@ public void testDeleteWithOrderMapping() throws ExecutionException, InterruptedE .createNamedDelete("delete-pokemon-full-order-arg") .indexedParam(pokemon) .execute() - ).toCompletableFuture().get(); + ).await(); verifyDeletePokemon(result, pokemon); } @@ -171,7 +171,7 @@ public void testDeleteWithNamedMapping() throws ExecutionException, InterruptedE .createNamedDelete("delete-pokemon-full-named-arg") .namedParam(pokemon) .execute() - ).toCompletableFuture().get(); + ).await(); verifyDeletePokemon(result, pokemon); } diff --git a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/tests/metrics/ServerMetricsCheckIT.java b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/tests/metrics/ServerMetricsCheckIT.java index d3fa409b5e2..4d85c65a2dd 100644 --- a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/tests/metrics/ServerMetricsCheckIT.java +++ b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/tests/metrics/ServerMetricsCheckIT.java @@ -29,7 +29,6 @@ import javax.json.Json; import javax.json.JsonObject; import javax.json.JsonReader; -import javax.json.JsonStructure; import javax.json.JsonValue; import javax.json.stream.JsonParsingException; @@ -38,8 +37,7 @@ import io.helidon.dbclient.DbClient; import io.helidon.dbclient.DbRow; import io.helidon.dbclient.DbStatementType; -import io.helidon.dbclient.metrics.DbCounter; -import io.helidon.dbclient.metrics.DbTimer; +import io.helidon.dbclient.metrics.DbClientMetrics; import io.helidon.metrics.MetricsSupport; import io.helidon.tests.integration.dbclient.common.AbstractIT; import io.helidon.tests.integration.dbclient.common.AbstractIT.Pokemon; @@ -89,9 +87,11 @@ private static DbClient initDbClient() { Config dbConfig = AbstractIT.CONFIG.get("db"); return DbClient.builder(dbConfig) // add an interceptor to named statement(s) - .addInterceptor(DbCounter.create(), "select-pokemons", "insert-pokemon") + .addService(DbClientMetrics.counter() + .statementNames("select-pokemons", "insert-pokemon")) // add an interceptor to statement type(s) - .addInterceptor(DbTimer.create(), DbStatementType.INSERT) + .addService(DbClientMetrics.timer() + .statementTypes(DbStatementType.INSERT)) .build(); } @@ -122,7 +122,9 @@ public static void startup() throws InterruptedException, ExecutionException { */ @AfterAll public static void shutdown() throws InterruptedException, ExecutionException { - SERVER.shutdown().toCompletableFuture().get(); + if (null != SERVER) { + SERVER.shutdown().toCompletableFuture().get(); + } } /** @@ -148,10 +150,9 @@ private static String get(String url) throws IOException, InterruptedException { * * @throws InterruptedException if the current thread was interrupted * @throws IOException if an I/O error occurs when sending or receiving HTTP request - * @throws ExecutionException when database query failed */ @Test - public void testHttpMetrics() throws IOException, InterruptedException, ExecutionException { + public void testHttpMetrics() throws IOException, InterruptedException { // Call select-pokemons to trigger it Multi rows = DB_CLIENT.execute(exec -> exec .namedQuery("select-pokemons")); @@ -161,20 +162,19 @@ public void testHttpMetrics() throws IOException, InterruptedException, Executio Pokemon pokemon = new Pokemon(BASE_ID + 1, "Lickitung", TYPES.get(1)); Long result = DB_CLIENT.execute(exec -> exec .namedInsert("insert-pokemon", pokemon.getId(), pokemon.getName()) - ).toCompletableFuture().get(); + ).await(); // Read and process metrics response - String response = get(URL + "/metrics"); + String response = get(URL + "/metrics/application"); LOGGER.info("RESPONSE: " + response); - JsonStructure jsonResponse = null; + JsonObject application = null; try (JsonReader jr = Json.createReader(new StringReader(response))) { - jsonResponse = jr.read(); + application = jr.readObject(); } catch (JsonParsingException | IllegalStateException ex) { fail(String.format("Error parsing response: %s", ex.getMessage())); } - assertThat(jsonResponse, notNullValue()); - assertThat(jsonResponse.getValueType(), equalTo(JsonValue.ValueType.OBJECT)); - assertThat(jsonResponse.asJsonObject().containsKey("application"), equalTo(true)); - JsonObject application = jsonResponse.asJsonObject().getJsonObject("application"); + assertThat(application, notNullValue()); + assertThat(application.getValueType(), equalTo(JsonValue.ValueType.OBJECT)); + assertThat(application.size(), greaterThan(0)); assertThat(application.containsKey("db.counter.select-pokemons"), equalTo(true)); assertThat(application.containsKey("db.counter.insert-pokemon"), equalTo(true)); diff --git a/tests/integration/kafka/pom.xml b/tests/integration/kafka/pom.xml index 7b6541374f3..0ec7aa36497 100644 --- a/tests/integration/kafka/pom.xml +++ b/tests/integration/kafka/pom.xml @@ -60,8 +60,34 @@ - org.apache.kafka - kafka-clients + io.helidon.messaging.kafka + helidon-messaging-kafka + + + io.helidon.messaging + helidon-messaging + + + io.helidon.microprofile.messaging + helidon-microprofile-messaging + + + org.eclipse.microprofile.reactive.messaging + microprofile-reactive-messaging-api + + + org.eclipse.microprofile.reactive-streams-operators + microprofile-reactive-streams-operators-api + + + org.eclipse.microprofile.reactive-streams-operators + microprofile-reactive-streams-operators-core + + + jakarta.enterprise + jakarta.enterprise.cdi-api + + org.apache.kafka @@ -79,18 +105,6 @@ kafka-junit5 test - - io.helidon.messaging.connectors.kafka - helidon-messaging-connectors-kafka - - - io.helidon.microprofile.reactive-streams - helidon-microprofile-reactive-streams - - - io.helidon.microprofile.messaging - helidon-microprofile-messaging - org.reactivestreams reactive-streams-tck @@ -101,23 +115,10 @@ mockito-core test - - org.eclipse.microprofile.reactive.messaging - microprofile-reactive-messaging-api - - - org.eclipse.microprofile.reactive-streams-operators - microprofile-reactive-streams-operators-api - - - org.eclipse.microprofile.reactive-streams-operators - microprofile-reactive-streams-operators-core - - - jakarta.enterprise - jakarta.enterprise.cdi-api - - + + org.hamcrest + hamcrest-all + test diff --git a/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/AbstractKafkaTest.java b/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/AbstractKafkaTest.java new file mode 100644 index 00000000000..d575d173fe2 --- /dev/null +++ b/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/AbstractKafkaTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging.connectors.kafka; + +import com.salesforce.kafka.test.junit5.SharedKafkaTestResource; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; + +public abstract class AbstractKafkaTest { + + static String KAFKA_SERVER; + + @RegisterExtension + public static final SharedKafkaTestResource kafkaResource = new SharedKafkaTestResource() + .withBrokers(4) + .withBrokerProperty("replication.factor", "2") + .withBrokerProperty("min.insync.replicas", "1") + .withBrokerProperty("auto.create.topics.enable", Boolean.toString(false)); + + @BeforeAll + static void prepareTopics() { + KAFKA_SERVER = kafkaResource.getKafkaConnectString(); + } +} diff --git a/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/AbstractSampleBean.java b/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/AbstractSampleBean.java index 76a4dbfc0c9..1109f44cb1b 100644 --- a/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/AbstractSampleBean.java +++ b/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/AbstractSampleBean.java @@ -88,10 +88,10 @@ public static class Channel1 extends AbstractSampleBean { @Incoming("test-channel-1") @Acknowledgment(Acknowledgment.Strategy.MANUAL) - public CompletionStage channel1(Message> msg) + public CompletionStage channel1(KafkaMessage msg) throws InterruptedException, ExecutionException { - LOGGER.fine(() -> String.format("Received %s", msg.getPayload().value())); - consumed().add(msg.getPayload().value()); + LOGGER.fine(() -> String.format("Received %s", msg.getPayload())); + consumed().add(msg.getPayload()); msg.ack(); countDown("channel1()"); return CompletableFuture.completedFuture(null); @@ -104,18 +104,18 @@ public static class ChannelProcessor extends AbstractSampleBean { @Incoming("test-channel-2") @Outgoing("test-channel-3") @Acknowledgment(Acknowledgment.Strategy.MANUAL) - public Message channel2ToChannel3(Message> msg) + public Message channel2ToChannel3(KafkaMessage msg) throws InterruptedException, ExecutionException { msg.ack(); - return Message.of("Processed" + msg.getPayload().value()); + return Message.of("Processed" + msg.getPayload()); } @Incoming("test-channel-7") @Acknowledgment(Acknowledgment.Strategy.MANUAL) - public CompletionStage channel7(Message> msg) + public CompletionStage channel7(KafkaMessage msg) throws InterruptedException, ExecutionException { - LOGGER.fine(() -> String.format("Received %s", msg.getPayload().value())); - consumed().add(msg.getPayload().value()); + LOGGER.fine(() -> String.format("Received %s", msg.getPayload())); + consumed().add(msg.getPayload()); msg.ack().whenComplete((a, b) -> countDown("channel7()")); return CompletableFuture.completedFuture(null); } @@ -125,10 +125,10 @@ public CompletionStage channel7(Message> ms public static class ChannelError extends AbstractSampleBean { @Incoming("test-channel-error") @Acknowledgment(Acknowledgment.Strategy.MANUAL) - public CompletionStage error(Message> msg) { + public CompletionStage error(KafkaMessage msg) { try { - LOGGER.fine(() -> String.format("Received possible error %s", msg.getPayload().value())); - consumed().add(Integer.toString(Integer.parseInt(msg.getPayload().value()))); + LOGGER.fine(() -> String.format("Received possible error %s", msg.getPayload())); + consumed().add(Integer.toString(Integer.parseInt(msg.getPayload()))); } finally { msg.ack().whenComplete((a, b) -> countDown("error()")); } @@ -140,18 +140,18 @@ public CompletionStage error(Message> msg) public static class Channel4 extends AbstractSampleBean { @Incoming("test-channel-4") - public SubscriberBuilder>, Void> channel4() { + public SubscriberBuilder, Void> channel4() { LOGGER.fine(() -> "In channel4"); - return ReactiveStreams.>>builder() - .to(new Subscriber>>() { + return ReactiveStreams.>builder() + .to(new Subscriber>() { @Override public void onSubscribe(Subscription subscription) { subscription.request(3); } @Override - public void onNext(Message> msg) { - consumed().add(Integer.toString(Integer.parseInt(msg.getPayload().value()))); - LOGGER.fine(() -> "Added " + msg.getPayload().value()); + public void onNext(KafkaMessage msg) { + consumed().add(Integer.toString(Integer.parseInt(msg.getPayload()))); + LOGGER.fine(() -> "Added " + msg.getPayload()); msg.ack(); countDown("onNext()"); } @@ -174,19 +174,19 @@ public void onComplete() { public static class Channel5 extends AbstractSampleBean { @Incoming("test-channel-5") - public SubscriberBuilder>, Void> channel5() { + public SubscriberBuilder, Void> channel5() { LOGGER.fine(() -> "In channel5"); - return ReactiveStreams.>>builder() - .to(new Subscriber>>() { + return ReactiveStreams.>builder() + .to(new Subscriber>() { @Override public void onSubscribe(Subscription subscription) { LOGGER.fine(() -> "channel5 onSubscribe()"); subscription.request(3); } @Override - public void onNext(Message> msg) { - consumed().add(Integer.toString(Integer.parseInt(msg.getPayload().value()))); - LOGGER.fine(() -> "Added " + msg.getPayload().value()); + public void onNext(KafkaMessage msg) { + consumed().add(Integer.toString(Integer.parseInt(msg.getPayload()))); + LOGGER.fine(() -> "Added " + msg.getPayload()); msg.ack(); countDown("onNext()"); } @@ -211,11 +211,11 @@ public static class Channel6 extends AbstractSampleBean { @Incoming("test-channel-6") @Acknowledgment(Acknowledgment.Strategy.MANUAL) - public CompletionStage channel6(Message> msg) { - LOGGER.fine(() -> String.format("Received %s", msg.getPayload().value())); - consumed().add(msg.getPayload().value()); + public CompletionStage channel6(KafkaMessage msg) { + LOGGER.fine(() -> String.format("Received %s", msg.getPayload())); + consumed().add(msg.getPayload()); // Certain messages are not ACK. We can check later that they will be sent again. - if (!NO_ACK.equals(msg.getPayload().value())) { + if (!NO_ACK.equals(msg.getPayload())) { LOGGER.fine(() -> "ACK sent"); msg.ack(); } else { @@ -238,12 +238,12 @@ public static class Channel8 extends AbstractSampleBean { @Incoming("test-channel-8") @Acknowledgment(Acknowledgment.Strategy.MANUAL) - public CompletionStage channel6(Message> msg) { + public CompletionStage channel6(KafkaMessage msg) { ConsumerRecord record = msg.unwrap(ConsumerRecord.class); consumed().add(record.value()); // Certain messages are not ACK. We can check later that they will be sent again. - if (NO_ACK.equals(msg.getPayload().value())) { - LOGGER.fine(() -> String.format("NO_ACK. Received %s", msg.getPayload().value())); + if (NO_ACK.equals(msg.getPayload())) { + LOGGER.fine(() -> String.format("NO_ACK. Received %s", msg.getPayload())); partitionIdOfNoAck.set(record.partition()); uncommitted.getAndIncrement(); countDown("no_ack channel8()"); @@ -251,11 +251,11 @@ public CompletionStage channel6(Message> ms if (limit.getAndIncrement() == LIMIT) { throw new IllegalStateException("Avoid the Kafka rebalance fix"); }else if (record.partition() == partitionIdOfNoAck.get()) { - LOGGER.fine(() -> String.format("NO_ACK. Received %s", msg.getPayload().value())); + LOGGER.fine(() -> String.format("NO_ACK. Received %s", msg.getPayload())); uncommitted.getAndIncrement(); countDown("no_ack channel8()"); } else { - LOGGER.fine(() -> String.format("ACK. Received %s", msg.getPayload().value())); + LOGGER.fine(() -> String.format("ACK. Received %s", msg.getPayload())); msg.ack().whenComplete((a, b) -> countDown("ack channel8()")); } } @@ -273,13 +273,13 @@ public static class Channel9 extends AbstractSampleBean { @Incoming("test-channel-10") @Outgoing("test-channel-9") @Acknowledgment(Acknowledgment.Strategy.MANUAL) - public Message channel10ToChannel9(Message> msg) { + public Message channel10ToChannel9(KafkaMessage msg) { msg.ack().whenComplete((a, b) -> { - consumed().add(msg.getPayload().value()); - LOGGER.fine(() -> "Added " + msg.getPayload().value()); + consumed().add(msg.getPayload()); + LOGGER.fine(() -> "Added " + msg.getPayload()); countDown("channel10ToChannel9()"); }); - return Message.of(msg.getPayload().value()); + return Message.of(msg.getPayload()); } } @@ -288,7 +288,7 @@ public static class Channel11 extends AbstractSampleBean { @Incoming("test-channel-11") @Acknowledgment(Acknowledgment.Strategy.MANUAL) - public CompletionStage channel1(Message> msg) { + public CompletionStage channel1(KafkaMessage msg) { // Unreachable because the Kafka connection is invalid return CompletableFuture.completedFuture(null); } @@ -300,13 +300,13 @@ public static class Channel12 extends AbstractSampleBean { @Incoming("test-channel-13") @Outgoing("test-channel-12") @Acknowledgment(Acknowledgment.Strategy.MANUAL) - public Message channel13ToChannel12(Message> msg) { + public Message channel13ToChannel12(KafkaMessage msg) { msg.ack().whenComplete((a, b) -> { - consumed().add(msg.getPayload().value()); - LOGGER.fine(() -> "Added " + msg.getPayload().value()); + consumed().add(msg.getPayload()); + LOGGER.fine(() -> "Added " + msg.getPayload()); countDown("channel13ToChannel12()"); }); - return Message.of(msg.getPayload().value()); + return Message.of(msg.getPayload()); } } } diff --git a/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/KafkaCdiExtensionTest.java b/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/KafkaMpTest.java similarity index 99% rename from tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/KafkaCdiExtensionTest.java rename to tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/KafkaMpTest.java index 7aa40393ca1..a9966983f76 100644 --- a/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/KafkaCdiExtensionTest.java +++ b/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/KafkaMpTest.java @@ -75,10 +75,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -class KafkaCdiExtensionTest { +class KafkaMpTest extends AbstractKafkaTest{ - private static final Logger LOGGER = Logger.getLogger(KafkaCdiExtensionTest.class.getName()); - private static final Connector KAFKA_CONNECTOR_LITERAL = new Connector() { + private static final Logger LOGGER = Logger.getLogger(KafkaMpTest.class.getName()); + protected static final Connector KAFKA_CONNECTOR_LITERAL = new Connector() { @Override public Class annotationType() { @@ -108,7 +108,6 @@ public String value() { private static final String GROUP_1 = "group1"; private static final String GROUP_2 = "group2"; - private static String KAFKA_SERVER; private static SeContainer cdiContainer; private static Map cdiConfig() { @@ -370,6 +369,7 @@ void withBackPressureAndError() { } @Test + @Disabled("It fails sometimes. Needs to be solved in otherway.") void someEventsNoAckWithOnePartition() { LOGGER.fine(() -> "==========> test someEventsNoAckWithOnePartition()"); List uncommit = new ArrayList<>(); diff --git a/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/KafkaSeTest.java b/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/KafkaSeTest.java new file mode 100644 index 00000000000..0a2cc6d0a76 --- /dev/null +++ b/tests/integration/kafka/src/test/java/io/helidon/messaging/connectors/kafka/KafkaSeTest.java @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.messaging.connectors.kafka; + +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import io.helidon.common.reactive.Multi; +import io.helidon.config.Config; +import io.helidon.messaging.Channel; +import io.helidon.messaging.Messaging; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import org.apache.kafka.common.serialization.IntegerDeserializer; +import org.apache.kafka.common.serialization.IntegerSerializer; +import org.apache.kafka.common.serialization.LongDeserializer; +import org.apache.kafka.common.serialization.LongSerializer; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class KafkaSeTest extends AbstractKafkaTest { + private static final Logger LOGGER = Logger.getLogger(KafkaSeTest.class.getName()); + private static final String TEST_SE_TOPIC_1 = "special-se-topic-1"; + private static final String TEST_SE_TOPIC_2 = "special-se-topic-2"; + private static final String TEST_SE_TOPIC_3 = "special-se-topic-3"; + private static final String TEST_SE_TOPIC_4 = "special-se-topic-4"; + private static final String TEST_SE_TOPIC_5 = "special-se-topic-4"; + + + @BeforeAll + static void prepareTopics() { + kafkaResource.getKafkaTestUtils().createTopic(TEST_SE_TOPIC_1, 4, (short) 2); + kafkaResource.getKafkaTestUtils().createTopic(TEST_SE_TOPIC_2, 4, (short) 2); + kafkaResource.getKafkaTestUtils().createTopic(TEST_SE_TOPIC_3, 4, (short) 2); + kafkaResource.getKafkaTestUtils().createTopic(TEST_SE_TOPIC_4, 4, (short) 2); + kafkaResource.getKafkaTestUtils().createTopic(TEST_SE_TOPIC_5, 4, (short) 2); + KAFKA_SERVER = kafkaResource.getKafkaConnectString(); + } + + @Test + void sendToKafka() throws InterruptedException { + + Channel toKafka = Channel.builder() + .name("to-kafka") + .subscriberConfig(KafkaConnector.configBuilder() + .bootstrapServers(KAFKA_SERVER) + .groupId("test-group") + .topic(TEST_SE_TOPIC_2) + .acks("all") + .property("retries", "2") + .keySerializer(LongSerializer.class) + .valueSerializer(IntegerSerializer.class) + .build() + ).build(); + + KafkaConnector kafkaConnector = KafkaConnector.create(Config.empty()); + + CountDownLatch countDownLatch = new CountDownLatch(10); + HashSet result = new HashSet<>(); + + Messaging messaging = Messaging.builder() + .connector(kafkaConnector) + .publisher(toKafka, + Multi.create(IntStream.rangeClosed(0, 100).boxed()) + .map(Message::of) + ) + .build(); + + try { + messaging.start(); + IntegerDeserializer deserializer = new IntegerDeserializer(); + kafkaResource.getKafkaTestUtils().consumeAllRecordsFromTopic(TEST_SE_TOPIC_2).forEach(consumerRecord -> { + result.add(deserializer.deserialize(TEST_SE_TOPIC_2, consumerRecord.value())); + countDownLatch.countDown(); + }); + assertThat(countDownLatch.await(20, TimeUnit.SECONDS), is(true)); + assertThat(result, equalTo(IntStream.range(0, 100).boxed().collect(Collectors.toSet()))); + } finally { + messaging.stop(); + } + } + + @Test + void consumeKafka() throws InterruptedException { + Map testData = IntStream.rangeClosed(0, 10) + .boxed() + .collect(Collectors.toMap(String::valueOf, String::valueOf)); + + CountDownLatch countDownLatch = new CountDownLatch(testData.size()); + HashSet result = new HashSet<>(); + + Channel fromKafka = Channel.builder() + .name("from-kafka") + .publisherConfig(KafkaConnector.configBuilder() + .bootstrapServers(KAFKA_SERVER) + .groupId("test-group") + .topic(TEST_SE_TOPIC_1) + .autoOffsetReset(KafkaConfigBuilder.AutoOffsetReset.EARLIEST) + .enableAutoCommit(false) + .keyDeserializer(StringDeserializer.class) + .valueDeserializer(StringDeserializer.class) + .build() + ) + .build(); + + KafkaConnector kafkaConnector = KafkaConnector.create(); + + Messaging messaging = Messaging.builder() + .connector(kafkaConnector) + .listener(fromKafka, payload -> { + LOGGER.info("Kafka says: " + payload); + result.add(payload); + countDownLatch.countDown(); + }) + .build(); + + try { + messaging.start(); + Map rawTestData = testData.entrySet().stream() + .collect(Collectors.toMap(e -> e.getKey().getBytes(), e -> e.getValue().getBytes())); + kafkaResource.getKafkaTestUtils().produceRecords(rawTestData, TEST_SE_TOPIC_1, 1); + + assertThat(countDownLatch.await(20, TimeUnit.SECONDS), is(true)); + assertThat(result, containsInAnyOrder(testData.values().toArray())); + } finally { + messaging.stop(); + } + } + + @Test + void consumeKafkaMultipleTopics() throws InterruptedException { + Set testData1 = Set.of("0", "2", "3", "4", "5"); + Set testData2 = Set.of("6", "7", "8", "9", "10"); + + Set expected = new HashSet<>(testData1); + expected.addAll(testData2); + + CountDownLatch countDownLatch = new CountDownLatch(expected.size()); + HashSet result = new HashSet<>(); + + Channel fromKafka = Channel.builder() + .name("from-kafka") + .publisherConfig(KafkaConnector.configBuilder() + .bootstrapServers(KAFKA_SERVER) + .groupId("test-group") + .topic(TEST_SE_TOPIC_3, TEST_SE_TOPIC_4) + .autoOffsetReset(KafkaConfigBuilder.AutoOffsetReset.EARLIEST) + .enableAutoCommit(false) + .keyDeserializer(StringDeserializer.class) + .valueDeserializer(StringDeserializer.class) + .build() + ) + .build(); + + KafkaConnector kafkaConnector = KafkaConnector.create(); + + Messaging messaging = Messaging.builder() + .connector(kafkaConnector) + .listener(fromKafka, payload -> { + LOGGER.info("Kafka says: value=" + payload); + result.add(payload); + countDownLatch.countDown(); + }) + .build(); + + try { + messaging.start(); + + Map rawTestData1 = testData1.stream() + .map(s -> s.getBytes(StandardCharsets.UTF_8)) + .collect(Collectors.toMap(Function.identity(), Function.identity())); + Map rawTestData2 = testData2.stream() + .map(s -> s.getBytes(StandardCharsets.UTF_8)) + .collect(Collectors.toMap(Function.identity(), Function.identity())); + kafkaResource.getKafkaTestUtils().produceRecords(rawTestData1, TEST_SE_TOPIC_3, 1); + kafkaResource.getKafkaTestUtils().produceRecords(rawTestData2, TEST_SE_TOPIC_4, 1); + + assertThat(countDownLatch.await(20, TimeUnit.SECONDS), is(true)); + assertThat(result, containsInAnyOrder(expected.toArray())); + } finally { + messaging.stop(); + } + } + + @Test + void kafkaEcho() throws InterruptedException { + CountDownLatch countDownLatch = new CountDownLatch(10); + HashSet result = new HashSet<>(); + + Channel toKafka = Channel.builder() + .name("to-kafka") + .subscriberConfig(KafkaConnector.configBuilder() + .bootstrapServers(KAFKA_SERVER) + .groupId("test-group") + .topic(TEST_SE_TOPIC_1) + .keySerializer(LongSerializer.class) + .valueSerializer(IntegerSerializer.class) + .build() + ).build(); + + Channel fromKafka = Channel.builder() + .name("from-kafka") + .publisherConfig(KafkaConnector.configBuilder() + .bootstrapServers(KAFKA_SERVER) + .groupId("test-group") + .topic(TEST_SE_TOPIC_1) + .autoOffsetReset(KafkaConfigBuilder.AutoOffsetReset.EARLIEST) + .enableAutoCommit(false) + .keyDeserializer(LongDeserializer.class) + .valueDeserializer(IntegerDeserializer.class) + .build() + ) + .build(); + + KafkaConnector kafkaConnector = KafkaConnector.create(); + + Messaging messaging = Messaging.builder() + .connector(kafkaConnector) + .publisher(toKafka, + Multi.create(IntStream.rangeClosed(0, 100).boxed()) + .map(Message::of) + ) + .subscriber(fromKafka, ReactiveStreams.>builder() + .peek(Message::ack) + .map(Message::getPayload) + .filter(i -> i < 10) + .forEach(payload -> { + LOGGER.info("Kafka says: " + payload); + result.add(payload); + countDownLatch.countDown(); + })) + .build(); + + try { + messaging.start(); + assertThat(countDownLatch.await(20, TimeUnit.SECONDS), is(true)); + assertThat(result, containsInAnyOrder(IntStream.range(0, 10).boxed().toArray())); + } finally { + messaging.stop(); + } + } + + @Test + void kafkaHeaderTest() throws InterruptedException { + CountDownLatch countDownLatch = new CountDownLatch(3); + HashSet result = new HashSet<>(); + + Channel toKafka = Channel.builder() + .name("to-kafka") + .subscriberConfig(KafkaConnector.configBuilder() + .bootstrapServers(KAFKA_SERVER) + .groupId("test-group") + .topic(TEST_SE_TOPIC_1) + .keySerializer(LongSerializer.class) + .valueSerializer(IntegerSerializer.class) + .build() + ).build(); + + Channel fromKafka = Channel.builder() + .name("from-kafka") + .publisherConfig(KafkaConnector.configBuilder() + .bootstrapServers(KAFKA_SERVER) + .groupId("test-group") + .topic(TEST_SE_TOPIC_1) + .autoOffsetReset(KafkaConfigBuilder.AutoOffsetReset.EARLIEST) + .enableAutoCommit(false) + .keyDeserializer(LongDeserializer.class) + .valueDeserializer(IntegerDeserializer.class) + .build() + ) + .build(); + + KafkaConnector kafkaConnector = KafkaConnector.create(); + + Messaging messaging = Messaging.builder() + .connector(kafkaConnector) + .publisher(toKafka, + Multi.from(IntStream.rangeClosed(1, 3).boxed()) + .map(KafkaMessage::of) + .peek(msg -> msg.getHeaders() + .add("secret header", + ("number: " + msg.getPayload()).getBytes(StandardCharsets.UTF_8)) + ) + ) + .subscriber(fromKafka, ReactiveStreams.>builder() + .peek(Message::ack) + .forEach(msg -> { + byte[] headerRawValue = msg.getHeaders().lastHeader("secret header").value(); + String value = new String(headerRawValue, StandardCharsets.UTF_8); + result.add(value); + countDownLatch.countDown(); + })) + .build(); + + try { + messaging.start(); + assertThat(countDownLatch.await(20, TimeUnit.SECONDS), is(true)); + assertThat(result, containsInAnyOrder( + "number: 1", + "number: 2", + "number: 3" + )); + } finally { + messaging.stop(); + } + } +} diff --git a/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/metrics/MetricsConfigurerTest.java b/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/metrics/MetricsConfigurerTest.java index 822c5654136..047d3454760 100644 --- a/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/metrics/MetricsConfigurerTest.java +++ b/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/metrics/MetricsConfigurerTest.java @@ -24,7 +24,7 @@ import io.helidon.grpc.server.MethodDescriptor; import io.helidon.grpc.server.ServiceDescriptor; import io.helidon.metrics.RegistryFactory; -import io.helidon.microprofile.grpc.core.RpcService; +import io.helidon.microprofile.grpc.core.Grpc; import io.helidon.microprofile.grpc.core.Unary; import io.grpc.ServerInterceptor; @@ -222,7 +222,7 @@ public void shouldIgnoreTimerMetricFromInterfaceAnnotation() { } - @RpcService + @Grpc public static class ServiceOne implements GrpcService { @Override @@ -248,7 +248,7 @@ public void metered(String request, StreamObserver response) { } } - @RpcService + @Grpc @SuppressWarnings("CdiManagedBeanInconsistencyInspection") public interface ServiceTwo { diff --git a/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/metrics/MetricsTest.java b/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/metrics/MetricsTest.java index ae6a16fa099..3270b037d0d 100644 --- a/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/metrics/MetricsTest.java +++ b/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/metrics/MetricsTest.java @@ -57,7 +57,7 @@ * Functional test of gRPC MP metrics. *

* The {@link services.EchoService} and {@link services.StringService }beans should be discovered and deployed - * automatically as they are annotated with both the {@link io.helidon.microprofile.grpc.core.RpcService} annotation + * automatically as they are annotated with both the {@link io.helidon.microprofile.grpc.core.Grpc} annotation * and the {@link javax.enterprise.context.ApplicationScoped} annotation. Their methods are annotated with various * microprofile metrics annotations so they should automatically generate the correct metrics. */ diff --git a/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/server/AnnotatedServiceTest.java b/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/server/AnnotatedServiceTest.java index 100feda1b9f..e09ada39817 100644 --- a/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/server/AnnotatedServiceTest.java +++ b/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/server/AnnotatedServiceTest.java @@ -36,7 +36,7 @@ import io.helidon.grpc.server.ServiceDescriptor; import io.helidon.microprofile.grpc.core.Bidirectional; import io.helidon.microprofile.grpc.core.ClientStreaming; -import io.helidon.microprofile.grpc.core.RpcService; +import io.helidon.microprofile.grpc.core.Grpc; import io.helidon.microprofile.grpc.core.ServerStreaming; import io.helidon.microprofile.grpc.core.Unary; import io.helidon.microprofile.grpc.server.test.BidiServiceGrpc; @@ -302,7 +302,7 @@ private static ServiceDescriptor descriptor(Class cls) { /** * The unary methods service implementation. */ - @RpcService + @Grpc public static class UnaryService { @Unary public Services.TestResponse requestResponse(Services.TestRequest request) { @@ -360,7 +360,7 @@ public void unaryFutureNoRequest(CompletableFuture future) { /** * The server streaming service. */ - @RpcService + @Grpc public static class ServerStreamingService { @ServerStreaming @@ -396,7 +396,7 @@ private TestResponse response(String text) { /** * The client streaming service. */ - @RpcService + @Grpc public static class ClientStreamingService { @ClientStreaming public StreamObserver streaming(StreamObserver observer) { @@ -436,7 +436,7 @@ private TestResponse response(String text) { /** * The bi-directional streaming service. */ - @RpcService + @Grpc public static class BidiService { @Bidirectional public StreamObserver bidi(StreamObserver observer) { diff --git a/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/server/GrpcServerCdiExtensionTest.java b/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/server/GrpcServerCdiExtensionTest.java index 671b381feb1..20d051af5cc 100644 --- a/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/server/GrpcServerCdiExtensionTest.java +++ b/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/server/GrpcServerCdiExtensionTest.java @@ -28,7 +28,7 @@ import io.helidon.grpc.server.GrpcService; import io.helidon.grpc.server.ServiceDescriptor; import io.helidon.microprofile.grpc.core.InProcessGrpcChannel; -import io.helidon.microprofile.grpc.core.RpcService; +import io.helidon.microprofile.grpc.core.Grpc; import io.helidon.microprofile.grpc.core.Unary; import io.helidon.microprofile.grpc.server.spi.GrpcMpContext; import io.helidon.microprofile.grpc.server.spi.GrpcMpExtension; @@ -182,7 +182,7 @@ public void configure(GrpcMpContext context) { * Annotated gRPC service bean that should be discovered and deployed. */ @ApplicationScoped - @RpcService + @Grpc public static class UnaryService { @Unary public TestResponse requestResponse(TestRequest request) { @@ -208,7 +208,7 @@ public void streaming(TestRequest request, StreamObserver observer * A POJO protocol buffer generated service implementation * manually deployed by {@link ExtensionTwo}. */ - @RpcService + @Grpc public static class TestService implements GrpcService { diff --git a/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/server/InterceptorsTest.java b/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/server/InterceptorsTest.java index b2b2aee0486..c968b6660ec 100644 --- a/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/server/InterceptorsTest.java +++ b/tests/integration/mp-grpc/src/test/java/io/helidon/microprofile/grpc/server/InterceptorsTest.java @@ -28,7 +28,7 @@ import io.helidon.microprofile.grpc.core.GrpcInterceptor; import io.helidon.microprofile.grpc.core.GrpcInterceptorBinding; import io.helidon.microprofile.grpc.core.GrpcInterceptors; -import io.helidon.microprofile.grpc.core.RpcService; +import io.helidon.microprofile.grpc.core.Grpc; import io.helidon.microprofile.grpc.core.Unary; import io.grpc.Metadata; @@ -232,7 +232,7 @@ private int sizeOf(PriorityBag priorityBag) { int id() default 0; } - @RpcService + @Grpc @InterceptorOne @ApplicationScoped public static class InterceptedServiceOne { @@ -241,7 +241,7 @@ public void foo() { } } - @RpcService + @Grpc @ApplicationScoped public static class InterceptedServiceTwo { @Unary @@ -254,7 +254,7 @@ public void bar() { } } - @RpcService + @Grpc @InterceptorOne @ApplicationScoped public static class InterceptedServiceThree { @@ -268,7 +268,7 @@ public void bar() { } } - @RpcService + @Grpc @InterceptorThree(id = 2) @ApplicationScoped public static class InterceptedServiceFour { @@ -277,7 +277,7 @@ public void foo() { } } - @RpcService + @Grpc @ApplicationScoped @GrpcInterceptors({ServerInterceptorOne.class, ServerInterceptorTwo.class}) public static class InterceptedServiceFive { @@ -286,7 +286,7 @@ public void foo() { } } - @RpcService + @Grpc @ApplicationScoped public static class InterceptedServiceSix { @Unary @@ -295,7 +295,7 @@ public void foo() { } } - @RpcService + @Grpc @ApplicationScoped @GrpcInterceptors(ServerInterceptorFive.class) public static class InterceptedServiceSeven { @@ -304,7 +304,7 @@ public void foo() { } } - @RpcService + @Grpc @ApplicationScoped public static class InterceptedServiceEight { @Unary diff --git a/tests/integration/mp-grpc/src/test/java/services/EchoService.java b/tests/integration/mp-grpc/src/test/java/services/EchoService.java index 9686bcab561..e60ef47dc5d 100644 --- a/tests/integration/mp-grpc/src/test/java/services/EchoService.java +++ b/tests/integration/mp-grpc/src/test/java/services/EchoService.java @@ -20,7 +20,7 @@ import io.helidon.grpc.server.test.Echo.EchoRequest; import io.helidon.grpc.server.test.Echo.EchoResponse; -import io.helidon.microprofile.grpc.core.RpcService; +import io.helidon.microprofile.grpc.core.Grpc; import io.helidon.microprofile.grpc.core.Unary; import io.grpc.stub.StreamObserver; @@ -31,7 +31,7 @@ /** * A simple test gRPC echo service. */ -@RpcService +@Grpc @ApplicationScoped public class EchoService { diff --git a/tests/integration/mp-grpc/src/test/java/services/StringService.java b/tests/integration/mp-grpc/src/test/java/services/StringService.java index 680e18172a1..9490b06a3c8 100644 --- a/tests/integration/mp-grpc/src/test/java/services/StringService.java +++ b/tests/integration/mp-grpc/src/test/java/services/StringService.java @@ -25,7 +25,7 @@ import io.helidon.grpc.server.CollectingObserver; import io.helidon.microprofile.grpc.core.Bidirectional; import io.helidon.microprofile.grpc.core.ClientStreaming; -import io.helidon.microprofile.grpc.core.RpcService; +import io.helidon.microprofile.grpc.core.Grpc; import io.helidon.microprofile.grpc.core.ServerStreaming; import io.helidon.microprofile.grpc.core.Unary; @@ -40,7 +40,7 @@ /** * An implementation of the StringService with metrics annotated methods. */ -@RpcService +@Grpc @ApplicationScoped public class StringService { diff --git a/tests/integration/native-image/mp-1/logging.properties b/tests/integration/native-image/mp-1/logging.properties index 1ef5ed81a2c..8efee87e0d5 100644 --- a/tests/integration/native-image/mp-1/logging.properties +++ b/tests/integration/native-image/mp-1/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2020 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,7 +32,6 @@ io.helidon.level=INFO # It replaces "!thread!" with the current thread name #java.util.logging.ConsoleHandler.level=ALL io.helidon.common.HelidonConsoleHandler.level=ALL -#java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter 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 AUDIT.level=FINEST io.helidon.webserver.level=WARNING diff --git a/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/AutoFilter.java b/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/AutoFilter.java new file mode 100644 index 00000000000..14d907bcbf4 --- /dev/null +++ b/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/AutoFilter.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or 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.nativeimage.mp1; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.ext.Provider; + +@Provider +public class AutoFilter implements ContainerRequestFilter { + private static final AtomicInteger COUNT = new AtomicInteger(); + + @Override + public void filter(ContainerRequestContext requestContext) { + COUNT.incrementAndGet(); + } + + static int count() { + return COUNT.get(); + } +} diff --git a/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/HelloApplication.java b/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/JaxRsApplicationNoClasses.java similarity index 66% rename from tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/HelloApplication.java rename to tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/JaxRsApplicationNoClasses.java index e90884abb2a..2b118a6afd2 100644 --- a/tests/functional/context-propagation/src/main/java/io/helidon/tests/functional/context/hello/HelloApplication.java +++ b/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/JaxRsApplicationNoClasses.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,20 +14,14 @@ * limitations under the License. */ -package io.helidon.tests.functional.context.hello; - -import java.util.Set; +package io.helidon.tests.integration.nativeimage.mp1; import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; -/** - * HelloApplication class. - */ @ApplicationScoped -public class HelloApplication extends Application { - @Override - public Set> getClasses() { - return Set.of(HelloResource.class); - } +@ApplicationPath("/noclass") +public class JaxRsApplicationNoClasses extends Application { + // classes should be picked-up automatically from all resource and provider classes on classpath } diff --git a/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/Mp1Main.java b/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/Mp1Main.java index 564baf59c89..bc5925dc273 100644 --- a/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/Mp1Main.java +++ b/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/Mp1Main.java @@ -21,7 +21,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.Instant; -import java.util.ArrayList; import java.util.Base64; import java.util.HashMap; import java.util.LinkedList; @@ -53,8 +52,6 @@ import io.opentracing.Tracer; import io.opentracing.util.GlobalTracer; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; import static io.helidon.common.http.Http.Status.FORBIDDEN_403; @@ -111,11 +108,11 @@ public static void main(final String[] args) { long time = System.currentTimeMillis() - now; System.out.println("Tests finished in " + time + " millis"); - Config config = ConfigProvider.getConfig(); - List names = new ArrayList<>(); - config.getPropertyNames() - .forEach(names::add); - names.sort(String::compareTo); + // Config config = ConfigProvider.getConfig(); + // List names = new ArrayList<>(); + // config.getPropertyNames() + // .forEach(names::add); + // names.sort(String::compareTo); // System.out.println("All configuration options:"); // names.forEach(it -> { @@ -243,10 +240,38 @@ private static void testBean(int port, String jwtToken) { // Static content validateStaticContent(collector, target); + // Make sure resource and provider classes are discovered + validateNoClassApp(collector, target); + collector.collect() .checkValid(); } + private static void validateNoClassApp(Errors.Collector collector, WebTarget target) { + String path = "/noclass"; + String expected = "Hello World "; + + Response response = target.path(path) + .request() + .get(); + + if (response.getStatus() == OK_200.code()) { + String entity = response.readEntity(String.class); + if (!expected.equals(entity)) { + collector.fatal("Endpoint " + path + "should return \"" + expected + "\", but returned \"" + entity + "\""); + } + } else { + collector.fatal("Endpoint " + path + " should be handled by JaxRsResource.java. Status received: " + + response.getStatus()); + } + + int count = AutoFilter.count(); + + if (count == 0) { + collector.fatal("Filter should have been added to JaxRsApplicationNoClass automatically"); + } + } + private static void validateStaticContent(Errors.Collector collector, WebTarget target) { String path = "/static/resource.txt"; String expected = "classpath-resource-text"; diff --git a/tests/integration/native-image/se-1/pom.xml b/tests/integration/native-image/se-1/pom.xml index 66e8c2592fe..c2014f7f9de 100644 --- a/tests/integration/native-image/se-1/pom.xml +++ b/tests/integration/native-image/se-1/pom.xml @@ -65,8 +65,8 @@ helidon-webserver - io.helidon.media.jsonp - helidon-media-jsonp-server + io.helidon.media + helidon-media-jsonp io.helidon.config diff --git a/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/Se1Main.java b/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/Se1Main.java index 425e0dac680..d5f9112f0b2 100644 --- a/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/Se1Main.java +++ b/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/Se1Main.java @@ -23,7 +23,7 @@ import io.helidon.config.FileSystemWatcher; import io.helidon.health.HealthSupport; import io.helidon.health.checks.HealthChecks; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.metrics.MetricsSupport; import io.helidon.security.integration.webserver.WebSecurity; import io.helidon.tracing.TracerBuilder; diff --git a/tests/integration/webclient/src/main/java/io/helidon/tests/integration/webclient/GreetService.java b/tests/integration/webclient/src/main/java/io/helidon/tests/integration/webclient/GreetService.java index ca3e9ae431a..ae60b1a844f 100644 --- a/tests/integration/webclient/src/main/java/io/helidon/tests/integration/webclient/GreetService.java +++ b/tests/integration/webclient/src/main/java/io/helidon/tests/integration/webclient/GreetService.java @@ -31,6 +31,7 @@ import io.helidon.config.Config; import io.helidon.security.SecurityContext; import io.helidon.webclient.WebClient; +import io.helidon.webclient.security.WebClientSecurity; import io.helidon.webserver.Routing; import io.helidon.webserver.ServerRequest; import io.helidon.webserver.ServerResponse; @@ -84,6 +85,7 @@ public void update(Routing.Rules rules) { private void basicAuthOutbound(ServerRequest serverRequest, ServerResponse response) { WebClient webClient = WebClient.builder() .baseUri("http://localhost:" + Main.serverPort + "/greet/secure/basic") + .addService(WebClientSecurity.create()) .build(); webClient.get() diff --git a/tests/integration/webclient/src/main/java/io/helidon/tests/integration/webclient/Main.java b/tests/integration/webclient/src/main/java/io/helidon/tests/integration/webclient/Main.java index 4ecd0b24f26..c5e19b6a200 100644 --- a/tests/integration/webclient/src/main/java/io/helidon/tests/integration/webclient/Main.java +++ b/tests/integration/webclient/src/main/java/io/helidon/tests/integration/webclient/Main.java @@ -19,7 +19,7 @@ import java.util.concurrent.CompletionStage; import io.helidon.config.Config; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.security.integration.webserver.WebSecurity; import io.helidon.webserver.Routing; import io.helidon.webserver.WebServer; diff --git a/tests/integration/webclient/src/main/resources/application.yaml b/tests/integration/webclient/src/main/resources/application.yaml index 124cb334515..006f57335fd 100644 --- a/tests/integration/webclient/src/main/resources/application.yaml +++ b/tests/integration/webclient/src/main/resources/application.yaml @@ -24,6 +24,9 @@ server: client: follow-redirects: true max-redirects: 5 + services: + tracing: + security: security: providers: diff --git a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/MediaContextTest.java b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/MediaContextTest.java index 5be06cfbfa4..0fd85ddc5dc 100644 --- a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/MediaContextTest.java +++ b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/MediaContextTest.java @@ -26,7 +26,7 @@ import javax.json.JsonObject; import io.helidon.media.common.MediaContext; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; diff --git a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TestParent.java b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TestParent.java index c040845251e..92a966acec8 100644 --- a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TestParent.java +++ b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TestParent.java @@ -21,7 +21,7 @@ import io.helidon.common.context.Context; import io.helidon.config.Config; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.security.Security; import io.helidon.security.SecurityContext; import io.helidon.security.providers.httpauth.HttpBasicAuthProvider; @@ -85,7 +85,7 @@ protected static WebClient createNewClient(WebClientService... clientServices) { .context(context) .addMediaSupport(JsonpSupport.create()); - Stream.of(clientServices).forEach(builder::register); + Stream.of(clientServices).forEach(builder::addService); return builder.build(); } diff --git a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TracingPropagationTest.java b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TracingPropagationTest.java index bb17617d7a0..56469a43ac0 100644 --- a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TracingPropagationTest.java +++ b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TracingPropagationTest.java @@ -21,6 +21,7 @@ import java.util.concurrent.ExecutionException; import io.helidon.common.context.Context; +import io.helidon.config.Config; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientResponse; import io.helidon.webserver.WebServer; @@ -55,6 +56,7 @@ void testTracingSuccess() throws ExecutionException, InterruptedException { WebClient client = WebClient.builder() .baseUri(uri) .context(context) + .config(Config.create().get("client")) .build(); client.get() diff --git a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TracingTest.java b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TracingTest.java index a51707e2776..ae6b1f440f9 100644 --- a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TracingTest.java +++ b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TracingTest.java @@ -23,7 +23,7 @@ import javax.json.JsonObject; import io.helidon.common.context.Context; -import io.helidon.media.jsonp.common.JsonpSupport; +import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientResponse; @@ -53,6 +53,7 @@ void testTracingNoServerSuccess() throws ExecutionException, InterruptedExceptio .baseUri(uri) .context(context) .addMediaSupport(JsonpSupport.create()) + .config(CONFIG.get("client")) .build(); WebClientResponse response = client.get() @@ -93,6 +94,7 @@ void testTracingNoServerFailure() throws ExecutionException, InterruptedExceptio .baseUri("http://localhost:" + webServer.port() + "/greet") .context(context) .addMediaSupport(JsonpSupport.create()) + .config(CONFIG.get("client")) .build(); WebClientResponse response = client.get() diff --git a/tracing/jaeger/src/main/java/io/helidon/tracing/jaeger/JaegerTracerBuilder.java b/tracing/jaeger/src/main/java/io/helidon/tracing/jaeger/JaegerTracerBuilder.java index ae9f30f0fcd..a962cf2af7c 100644 --- a/tracing/jaeger/src/main/java/io/helidon/tracing/jaeger/JaegerTracerBuilder.java +++ b/tracing/jaeger/src/main/java/io/helidon/tracing/jaeger/JaegerTracerBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -354,19 +354,6 @@ public JaegerTracerBuilder config(Config config) { return this; } - /** - * The host name and port when using the remote controlled sampler. - * - * @param samplerManagerHostPort host and port of the sampler manager - * @return updated builder instance - * @deprecated typo, please use {@link #samplerManager(String)} - */ - @Deprecated - public JaegerTracerBuilder samplerMananger(String samplerManagerHostPort) { - this.samplerManager = samplerManagerHostPort; - return this; - } - /** * The host name and port when using the remote controlled sampler. * @@ -476,18 +463,6 @@ public Tracer build() { return result; } - @Override - public Tracer buildAndRegister() { - if (global) { - return build(); - } - - Tracer result = build(); - GlobalTracer.register(result); - - return result; - } - Configuration jaegerConfig() { /* * Preload from environment, then override configured values diff --git a/tracing/tests/it-tracing-client-zipkin/pom.xml b/tracing/tests/it-tracing-client-zipkin/pom.xml index 4d65e840387..bdb792304bf 100644 --- a/tracing/tests/it-tracing-client-zipkin/pom.xml +++ b/tracing/tests/it-tracing-client-zipkin/pom.xml @@ -1,6 +1,6 @@