basicRange = config.get("app.basic-range").asList(Integer.class).get();
+
+ Path loggingOutputPath = config.get("logging.outputs.file.name").as(Path.class).get();
+
+ System.out.println(pageSize);
+ System.out.println(storageEnabled);
+ System.out.println(basicRange);
+ System.out.println(loggingOutputPath);
+ }
+
+}
diff --git a/examples/config/basics/src/main/java/io/helidon/examples/config/basics/package-info.java b/examples/config/basics/src/main/java/io/helidon/examples/config/basics/package-info.java
new file mode 100644
index 000000000..f950c0a23
--- /dev/null
+++ b/examples/config/basics/src/main/java/io/helidon/examples/config/basics/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The simplest example shows how to use Configuration API.
+ */
+package io.helidon.examples.config.basics;
diff --git a/examples/config/basics/src/main/resources/application.conf b/examples/config/basics/src/main/resources/application.conf
new file mode 100644
index 000000000..8f283613f
--- /dev/null
+++ b/examples/config/basics/src/main/resources/application.conf
@@ -0,0 +1,69 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+app {
+ greeting = "Hello"
+ name = "Demo"
+ page-size = 20
+ basic-range = [ -20, 20 ]
+ storagePassphrase = "${AES=thisIsEncriptedPassphrase}"
+}
+
+logging {
+ outputs {
+ console {
+ pattern = simple.colored
+ level = INFO
+ }
+ file {
+ pattern = verbose.colored
+ level = DEBUG
+ name = target/root.log
+ }
+ }
+ level = INFO
+ app.level = DEBUG
+ com.oracle.prime.level = WARN
+}
+
+# (this is snippet of complex configuration of security component)
+security {
+ providers: [ # First provider in the list is the default one
+ {
+ name = "BMCS"
+ class = "com.oracle.prime.security.bmcs.BmcsProvider"
+ BmcsProvider {
+ # Configuration of OPC (Bare metal) security provider
+ # (configuration cleaned to be short ...)
+
+ # targets for outbound configuration
+ targets: [
+ {
+ name = "s2s"
+ transports = ["http"]
+ hosts = ["127.0.0.1"]
+ s2sType = "S2S"
+ }
+ #, other targets ...
+ ]
+ }
+ },
+ {
+ name = "ForEndUsers"
+ class = "com.oracle.prime.examples.security.primeruntime.bmcs.ExampleSecurityProvider"
+ }
+ ]
+}
diff --git a/examples/config/basics/src/main/resources/logging.properties b/examples/config/basics/src/main/resources/logging.properties
new file mode 100644
index 000000000..721cc5fc1
--- /dev/null
+++ b/examples/config/basics/src/main/resources/logging.properties
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+handlers = java.util.logging.ConsoleHandler
+
+java.util.logging.ConsoleHandler.level = FINEST
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+java.util.logging.SimpleFormatter.format = [%1$tc] %4$s: %2$s - %5$s %6$s%n
+
+.level = INFO
+io.helidon.config.level = WARNING
+io.helidon.config.examples.level = FINEST
diff --git a/examples/config/changes/README.md b/examples/config/changes/README.md
new file mode 100644
index 000000000..2f290e98b
--- /dev/null
+++ b/examples/config/changes/README.md
@@ -0,0 +1,29 @@
+# Helidon Config Changes Example
+
+This example shows how an application can deal with changes to
+configuration.
+
+## Change notification
+
+[`OnChangeExample.java`](src/main/java/io/helidon/examples/config/changes/OnChangeExample.java):
+uses `Config.onChange`, passing either a method reference (a lambda expression
+would also work) which the config system invokes when the config source changes
+)
+
+## Latest-value supplier
+
+Recall that once your application obtains a `Config` instance, its config values
+do not change. The
+[`AsSupplierExample.java`](src/main/java/io/helidon/examples/config/changes/AsSupplierExample.java)
+example shows how your application can get a config _supplier_ that always reports
+the latest config value for a key, including any changes made after your
+application obtained the `Config` object. Although this approach does not notify
+your application _when_ changes occur, it _does_ permit your code to always use
+the most up-to-date value. Sometimes that is all you need.
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-config-changes.jar
+```
diff --git a/examples/config/changes/conf/config.yaml b/examples/config/changes/conf/config.yaml
new file mode 100644
index 000000000..69d8030ed
--- /dev/null
+++ b/examples/config/changes/conf/config.yaml
@@ -0,0 +1,21 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# In config.yaml we are going to override default configuration.
+# It is supposed to be deployment dependent configuration.
+
+app:
+ greeting: Hello
diff --git a/examples/config/changes/conf/dev.yaml b/examples/config/changes/conf/dev.yaml
new file mode 100644
index 000000000..f2166b731
--- /dev/null
+++ b/examples/config/changes/conf/dev.yaml
@@ -0,0 +1,25 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# In dev.yaml we are going to override all lower layer configuration files.
+# It is supposed to be development specific configuration.
+
+# IT SHOULD NOT BE PLACED IN VCS. EACH DEVELOPER CAN CUSTOMIZE THE FILE AS NEEDED.
+# I.e. for example with GIT place following line into .gitignore file:
+#*/conf/dev.yaml
+
+app:
+ name: Developer
diff --git a/examples/config/changes/conf/secrets/password b/examples/config/changes/conf/secrets/password
new file mode 100644
index 000000000..5bbaf8758
--- /dev/null
+++ b/examples/config/changes/conf/secrets/password
@@ -0,0 +1 @@
+changeit
\ No newline at end of file
diff --git a/examples/config/changes/conf/secrets/username b/examples/config/changes/conf/secrets/username
new file mode 100644
index 000000000..1ce97e36c
--- /dev/null
+++ b/examples/config/changes/conf/secrets/username
@@ -0,0 +1 @@
+libor
\ No newline at end of file
diff --git a/examples/config/changes/pom.xml b/examples/config/changes/pom.xml
new file mode 100644
index 000000000..8a9843449
--- /dev/null
+++ b/examples/config/changes/pom.xml
@@ -0,0 +1,69 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.config
+ helidon-examples-config-changes
+ 1.0.0-SNAPSHOT
+ Helidon Examples Config Changes
+
+
+ The example shows how to use Configuration Changes API.
+
+
+
+ io.helidon.examples.config.changes.Main
+
+
+
+
+ io.helidon.config
+ helidon-config
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.common
+ helidon-common-reactive
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/config/changes/src/main/java/io/helidon/examples/config/changes/AsSupplierExample.java b/examples/config/changes/src/main/java/io/helidon/examples/config/changes/AsSupplierExample.java
new file mode 100644
index 000000000..08fe0ecc1
--- /dev/null
+++ b/examples/config/changes/src/main/java/io/helidon/examples/config/changes/AsSupplierExample.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.changes;
+
+import java.time.Duration;
+import java.util.Objects;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+import java.util.logging.Logger;
+
+import io.helidon.config.Config;
+import io.helidon.config.FileSystemWatcher;
+
+import static io.helidon.config.ConfigSources.classpath;
+import static io.helidon.config.ConfigSources.file;
+import static io.helidon.config.PollingStrategies.regular;
+
+/**
+ * Example shows how to use Config accessor methods that return {@link Supplier}.
+ * {@link Supplier} returns always the last loaded config value.
+ *
+ * The feature is based on using {@link io.helidon.config.spi.PollingStrategy} with
+ * selected config source(s) to check for changes.
+ */
+public class AsSupplierExample {
+
+ private static final Logger LOGGER = Logger.getLogger(AsSupplierExample.class.getName());
+
+ private final AtomicReference lastPrinted = new AtomicReference<>();
+ private final ScheduledExecutorService executor = initExecutor();
+
+ /**
+ * Executes the example.
+ */
+ public void run() {
+ Config config = Config
+ .create(file("conf/dev.yaml")
+ .optional()
+ // change watcher is a standalone component that watches for
+ // changes and notifies the config system when a change occurs
+ .changeWatcher(FileSystemWatcher.create()),
+ file("conf/config.yaml")
+ .optional()
+ // polling strategy triggers regular checks on the source to check
+ // for changes, utilizing a concept of "stamp" of the data that is provided
+ // and validated by the source
+ .pollingStrategy(regular(Duration.ofSeconds(2))),
+ classpath("default.yaml"));
+
+ // greeting.get() always return up-to-date value
+ final Supplier greeting = config.get("app.greeting").asString().supplier();
+ // name.get() always return up-to-date value
+ final Supplier name = config.get("app.name").asString().supplier();
+
+ // first greeting
+ printIfChanged(greeting.get() + " " + name.get() + ".");
+
+ // use same Supplier instances to get up-to-date value
+ executor.scheduleWithFixedDelay(
+ () -> printIfChanged(greeting.get() + " " + name.get() + "."),
+ // check every 1 second for changes
+ 0, 1, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Utility to print same message just once.
+ */
+ private void printIfChanged(String message) {
+ lastPrinted.accumulateAndGet(message, (origValue, newValue) -> {
+ //print MESSAGE only if changed since the last print
+ if (!Objects.equals(origValue, newValue)) {
+ LOGGER.info("[AsSupplier] " + newValue);
+ }
+ return newValue;
+ });
+ }
+
+ private static ScheduledExecutorService initExecutor() {
+ ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+ Runtime.getRuntime().addShutdownHook(new Thread(executor::shutdown));
+ return executor;
+ }
+
+ /**
+ * Shutdowns executor.
+ */
+ public void shutdown() {
+ executor.shutdown();
+ }
+
+}
diff --git a/examples/config/changes/src/main/java/io/helidon/examples/config/changes/Main.java b/examples/config/changes/src/main/java/io/helidon/examples/config/changes/Main.java
new file mode 100644
index 000000000..92127f132
--- /dev/null
+++ b/examples/config/changes/src/main/java/io/helidon/examples/config/changes/Main.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.changes;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+/**
+ * Config changes examples.
+ */
+public class Main {
+
+ private static final Logger LOGGER = Logger.getLogger(Main.class.getName());
+
+ private Main() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args arguments
+ * @throws InterruptedException in case you cannot sleep
+ * @throws IOException in case of IO error
+ */
+ public static void main(String... args) throws IOException, InterruptedException {
+ // subscribe using simple onChange function
+ new OnChangeExample().run();
+ // use same Supplier instances to get up-to-date value
+ AsSupplierExample asSupplier = new AsSupplierExample();
+ asSupplier.run();
+
+ // waiting for user made changes in config files
+ long sleep = 60;
+ LOGGER.info("Application is waiting " + sleep + " seconds for change...");
+ TimeUnit.SECONDS.sleep(sleep);
+
+ asSupplier.shutdown();
+ LOGGER.info("Goodbye.");
+ }
+
+}
diff --git a/examples/config/changes/src/main/java/io/helidon/examples/config/changes/OnChangeExample.java b/examples/config/changes/src/main/java/io/helidon/examples/config/changes/OnChangeExample.java
new file mode 100644
index 000000000..635bb0528
--- /dev/null
+++ b/examples/config/changes/src/main/java/io/helidon/examples/config/changes/OnChangeExample.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.changes;
+
+import java.util.logging.Logger;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+import io.helidon.config.PollingStrategies;
+
+import static java.time.Duration.ofSeconds;
+
+/**
+ * Example shows how to listen on Config node changes using simplified API, {@link Config#onChange(java.util.function.Consumer)}.
+ * The Function is invoked with new instance of Config.
+ *
+ * The feature is based on using {@link io.helidon.config.spi.PollingStrategy} with
+ * selected config source(s) to check for changes.
+ */
+public class OnChangeExample {
+
+ private static final Logger LOGGER = Logger.getLogger(OnChangeExample.class.getName());
+
+ /**
+ * Executes the example.
+ */
+ public void run() {
+ Config secrets = Config
+ .builder(ConfigSources.directory("conf/secrets")
+ .pollingStrategy(PollingStrategies.regular(ofSeconds(5))))
+ .disableEnvironmentVariablesSource()
+ .disableSystemPropertiesSource()
+ .build();
+
+ logSecrets(secrets);
+
+ // subscribe using simple onChange consumer -- could be a lambda as well
+ secrets.onChange(OnChangeExample::logSecrets);
+ }
+
+ private static void logSecrets(Config secrets) {
+ LOGGER.info("Loaded secrets are u: " + secrets.get("username").asString().get()
+ + ", p: " + secrets.get("password").asString().get());
+ }
+
+}
diff --git a/examples/config/changes/src/main/java/io/helidon/examples/config/changes/package-info.java b/examples/config/changes/src/main/java/io/helidon/examples/config/changes/package-info.java
new file mode 100644
index 000000000..3374e05ef
--- /dev/null
+++ b/examples/config/changes/src/main/java/io/helidon/examples/config/changes/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The example shows how to use Configuration Changes API.
+ */
+package io.helidon.examples.config.changes;
diff --git a/examples/config/changes/src/main/resources/default.yaml b/examples/config/changes/src/main/resources/default.yaml
new file mode 100644
index 000000000..32f1207f2
--- /dev/null
+++ b/examples/config/changes/src/main/resources/default.yaml
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# In default.yaml we are going to configure application default values.
+# It is expected it is overridden in:
+# - conf/config.yaml in production environment
+# - conf/dev.yaml by developer on local machine
+
+app:
+ greeting: Hi
+ name: Example
diff --git a/examples/config/changes/src/main/resources/logging.properties b/examples/config/changes/src/main/resources/logging.properties
new file mode 100644
index 000000000..1e6cab22c
--- /dev/null
+++ b/examples/config/changes/src/main/resources/logging.properties
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+handlers = java.util.logging.ConsoleHandler
+
+java.util.logging.ConsoleHandler.level = FINEST
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+java.util.logging.SimpleFormatter.format = [%1$tc] %5$s %6$s%n
+
+.level = INFO
+io.helidon.config.level = WARNING
+io.helidon.config.examples.level = FINEST
diff --git a/examples/config/git/README.md b/examples/config/git/README.md
new file mode 100644
index 000000000..efa442e8f
--- /dev/null
+++ b/examples/config/git/README.md
@@ -0,0 +1,28 @@
+# Helidon Config Git Example
+
+This example shows how to load configuration from a Git repository
+and switch which branch to load from at runtime.
+
+## Prerequisites
+
+The example assumes that the GitHub repository
+has a branch named `test` that contains `application.conf` which sets the key
+`greeting` to value `hello`. (The Helidon team has created and populated this
+repository.)
+
+The code in [`Main.java`](src/main/java/io/helidon/examples/config/git/Main.java)
+uses the environment variable `ENVIRONMENT_NAME` to fetch the branch name
+in the GitHub repository to use; it uses `master` by default (which does _not_
+contain the expected value).
+
+The example application constructs a `Config` instance from that file in the
+GitHub repository and branch, prints out the value for key `greeting`, and
+checks to make sure the value is the expected `hello`.
+
+## Build and run
+
+```shell
+mvn package
+export ENVIRONMENT_NAME=test
+java -jar target/helidon-examples-config-git.jar
+```
diff --git a/examples/config/git/pom.xml b/examples/config/git/pom.xml
new file mode 100644
index 000000000..780ed697f
--- /dev/null
+++ b/examples/config/git/pom.xml
@@ -0,0 +1,82 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.config
+ helidon-examples-config-git
+ 1.0.0-SNAPSHOT
+ Helidon Examples Config Git
+
+
+ The example shows how to use GitConfigSource.
+
+
+
+ io.helidon.examples.config.git.Main
+
+
+
+
+ io.helidon.config
+ helidon-config
+
+
+ io.helidon.config
+ helidon-config-hocon
+
+
+ io.helidon.config
+ helidon-config-git
+
+
+ org.slf4j
+ slf4j-jdk14
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ test
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/config/git/src/main/java/io/helidon/examples/config/git/Main.java b/examples/config/git/src/main/java/io/helidon/examples/config/git/Main.java
new file mode 100644
index 000000000..2e1126d57
--- /dev/null
+++ b/examples/config/git/src/main/java/io/helidon/examples/config/git/Main.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.config.git;
+
+import java.io.IOException;
+import java.net.URI;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+import io.helidon.config.git.GitConfigSource;
+
+/**
+ * Git source example.
+ *
+ * This example expects:
+ *
+ * a Git repository {@code helidonrobot/test-config} which contains:
+ *
+ * the branch {@code test} containing {@code application.conf} which sets
+ * {@code greeting} to {@code hello},
+ * the branch {@code main} containing the file {@code application.conf}
+ * which sets the property {@code greeting} to any value other than
+ * {@code hello},
+ * optionally, any other branch in which {@code application.conf} sets
+ * {@code greeting} to {@code hello}.
+ *
+ * the environment variable {@code ENVIRONMENT_NAME} set to:
+ *
+ * {@code test}, or
+ * the name of the optional additional branch described above.
+ *
+ *
+ */
+public class Main {
+
+ private static final String ENVIRONMENT_NAME_PROPERTY = "ENVIRONMENT_NAME";
+
+ private Main() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args arguments
+ * @throws IOException when some git repo operation failed
+ */
+ public static void main(String... args) throws IOException {
+
+ // we expect a name of the current environment in envvar ENVIRONMENT_NAME
+ // in this example we just set envvar in maven plugin 'exec', but can be set in k8s pod via ConfigMap
+ Config env = Config.create(ConfigSources.environmentVariables());
+
+ String branch = env.get(ENVIRONMENT_NAME_PROPERTY).asString().orElse("master");
+
+ System.out.println("Loading from branch " + branch);
+
+ Config config = Config.create(
+ GitConfigSource.builder()
+ .path("application.conf")
+ .uri(URI.create("https://github.com/helidonrobot/test-config.git"))
+ .branch(branch)
+ .build());
+
+ System.out.println("Greeting is " + config.get("greeting").asString().get());
+ assert config.get("greeting").asString().get().equals("hello");
+ }
+
+}
diff --git a/examples/config/git/src/main/java/io/helidon/examples/config/git/package-info.java b/examples/config/git/src/main/java/io/helidon/examples/config/git/package-info.java
new file mode 100644
index 000000000..b98b46200
--- /dev/null
+++ b/examples/config/git/src/main/java/io/helidon/examples/config/git/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The example shows how to use GitConfigSource.
+ */
+package io.helidon.examples.config.git;
diff --git a/examples/config/git/src/main/resources/logging.properties b/examples/config/git/src/main/resources/logging.properties
new file mode 100644
index 000000000..721cc5fc1
--- /dev/null
+++ b/examples/config/git/src/main/resources/logging.properties
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+handlers = java.util.logging.ConsoleHandler
+
+java.util.logging.ConsoleHandler.level = FINEST
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+java.util.logging.SimpleFormatter.format = [%1$tc] %4$s: %2$s - %5$s %6$s%n
+
+.level = INFO
+io.helidon.config.level = WARNING
+io.helidon.config.examples.level = FINEST
diff --git a/examples/config/mapping/README.md b/examples/config/mapping/README.md
new file mode 100644
index 000000000..125233320
--- /dev/null
+++ b/examples/config/mapping/README.md
@@ -0,0 +1,23 @@
+# Helidon Config Mapping Example
+
+This example shows how to implement mappers that convert configuration
+to POJOs.
+
+1. [`BuilderExample.java`](src/main/java/io/helidon/examples/config/mapping/BuilderExample.java)
+shows how you can add a `builder()` method to a POJO. That method returns a `Builder`
+object which the config system uses to update with various key settings and then,
+finally, invoke `build()` so the builder can instantiate the POJO with the
+assigned values.
+2. [`DeserializationExample.java`](src/main/java/io/helidon/examples/config/mapping/DeserializationExample.java)
+uses the config system's support for automatic mapping to POJOs that have bean-style
+setter methods.
+3. [`FactoryMethodExample.java`](src/main/java/io/helidon/examples/config/mapping/FactoryMethodExample.java)
+illustrates how you can add a static factory method `create` to a POJO to tell the config
+system how to construct a POJO instance.
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-config-mapping.jar
+```
diff --git a/examples/config/mapping/pom.xml b/examples/config/mapping/pom.xml
new file mode 100644
index 000000000..110ac84a1
--- /dev/null
+++ b/examples/config/mapping/pom.xml
@@ -0,0 +1,69 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.config
+ helidon-examples-config-mapping
+ 1.0.0-SNAPSHOT
+ Helidon Examples Config Mapping
+
+
+ The example shows how to use Config Mapping functionality.
+
+
+
+ io.helidon.examples.config.mapping.Main
+
+
+
+
+ io.helidon.config
+ helidon-config
+
+
+ io.helidon.config
+ helidon-config-hocon
+
+
+ io.helidon.config
+ helidon-config-object-mapping
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/BuilderExample.java b/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/BuilderExample.java
new file mode 100644
index 000000000..0bfd9e4bf
--- /dev/null
+++ b/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/BuilderExample.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.mapping;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+import io.helidon.config.objectmapping.Value;
+
+/**
+ * This example shows how to automatically deserialize configuration instance into POJO beans
+ * using Builder pattern.
+ */
+public class BuilderExample {
+
+ private BuilderExample() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args arguments
+ */
+ public static void main(String... args) {
+ Config config = Config.create(ConfigSources.classpath("application.conf"));
+
+ AppConfig appConfig = config
+ // get "app" sub-node
+ .get("app")
+ // let config automatically deserialize the node to new AppConfig instance
+ // note that this requires additional dependency - config-beans
+ .as(AppConfig.class)
+ .get();
+
+ System.out.println(appConfig);
+
+ // assert values loaded from application.conf
+
+ assert appConfig.getGreeting().equals("Hello");
+
+ assert appConfig.getPageSize() == 20;
+
+ assert appConfig.getBasicRange().size() == 2;
+ assert appConfig.getBasicRange().get(0) == -20;
+ assert appConfig.getBasicRange().get(1) == 20;
+ }
+
+ /**
+ * POJO representing an application configuration.
+ * Class is initialized from {@link Config} instance.
+ * During deserialization {@link #builder()} builder method} is invoked
+ * and {@link Builder} is used to initialize properties from configuration.
+ */
+public static class AppConfig {
+ private final String greeting;
+ private final int pageSize;
+ private final List basicRange;
+
+ private AppConfig(String greeting, int pageSize, List basicRange) {
+ this.greeting = greeting;
+ this.pageSize = pageSize;
+ this.basicRange = basicRange;
+ }
+
+ public String getGreeting() {
+ return greeting;
+ }
+
+ public int getPageSize() {
+ return pageSize;
+ }
+
+ public List getBasicRange() {
+ return basicRange;
+ }
+
+ @Override
+ public String toString() {
+ return "AppConfig:\n"
+ + " greeting = " + greeting + "\n"
+ + " pageSize = " + pageSize + "\n"
+ + " basicRange= " + basicRange;
+ }
+
+ /**
+ * Creates new Builder instance used to be initialized from configuration.
+ *
+ * @return new Builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * {@link AppConfig} Builder used to be initialized from configuration.
+ */
+ public static class Builder {
+ private String greeting;
+ private int pageSize;
+ private List basicRange;
+
+ private Builder() {
+ }
+
+ /**
+ * Set greeting property.
+ *
+ * POJO property and config key are same, no need to customize it.
+ * {@link Value} is used just to specify default value
+ * in case configuration does not contain appropriate value.
+ *
+ * @param greeting greeting value
+ */
+ @Value(withDefault = "Hi")
+ public void setGreeting(String greeting) {
+ this.greeting = greeting;
+ }
+
+ /**
+ * Set a page size.
+ *
+ * {@link Value} is used to specify correct config key and default value
+ * in case configuration does not contain appropriate value.
+ * Original string value is mapped to target int using appropriate
+ * {@link io.helidon.config.ConfigMappers ConfigMapper}.
+ *
+ * @param pageSize page size
+ */
+ @Value(key = "page-size", withDefault = "10")
+ public void setPageSize(int pageSize) {
+ this.pageSize = pageSize;
+ }
+
+ /**
+ * Set a basic range.
+ *
+ * {@link Value} is used to specify correct config key and default value supplier
+ * in case configuration does not contain appropriate value.
+ * Supplier already returns default value in target type of a property.
+ *
+ * @param basicRange basic range
+ */
+ @Value(key = "basic-range", withDefaultSupplier = DefaultBasicRangeSupplier.class)
+ public void setBasicRange(List basicRange) {
+ this.basicRange = basicRange;
+ }
+
+ /**
+ * Creates new instance of {@link AppConfig} using values provided by configuration.
+ *
+ * @return new instance of {@link AppConfig}.
+ */
+ public AppConfig build() {
+ return new AppConfig(greeting, pageSize, basicRange);
+ }
+ }
+
+ /**
+ * Supplier of default value for {@link Builder#setBasicRange(List) basic-range} property.
+ */
+ public static class DefaultBasicRangeSupplier implements Supplier> {
+ @Override
+ public List get() {
+ return List.of(-10, 10);
+ }
+ }
+ }
+}
diff --git a/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/DeserializationExample.java b/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/DeserializationExample.java
new file mode 100644
index 000000000..4d1e79ca3
--- /dev/null
+++ b/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/DeserializationExample.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.mapping;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+import io.helidon.config.objectmapping.Value;
+
+/**
+ * This example shows how to automatically deserialize configuration instance into POJO beans
+ * using setters.
+ */
+public class DeserializationExample {
+
+ private DeserializationExample() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args arguments
+ */
+ public static void main(String... args) {
+ Config config = Config.create(ConfigSources.classpath("application.conf"));
+
+ AppConfig appConfig = config
+ // get "app" sub-node
+ .get("app")
+ // let config automatically deserialize the node to new AppConfig instance
+ .as(AppConfig.class)
+ .get();
+
+ System.out.println(appConfig);
+
+ // assert values loaded from application.conf
+
+ assert appConfig.getGreeting().equals("Hello");
+
+ assert appConfig.getPageSize() == 20;
+
+ assert appConfig.getBasicRange().size() == 2;
+ assert appConfig.getBasicRange().get(0) == -20;
+ assert appConfig.getBasicRange().get(1) == 20;
+ }
+
+ /**
+ * POJO representing an application configuration.
+ * Class is initialized from {@link Config} instance.
+ * During deserialization setter methods are invoked.
+ */
+ public static class AppConfig {
+ private String greeting;
+ private int pageSize;
+ private List basicRange;
+
+ public String getGreeting() {
+ return greeting;
+ }
+
+ /**
+ * Set greeting property.
+ *
+ * POJO property and config key are same, no need to customize it.
+ * {@link Value} is used just to specify default value
+ * in case configuration does not contain appropriate value.
+ *
+ * @param greeting greeting value
+ */
+ @Value(withDefault = "Hi")
+ public void setGreeting(String greeting) {
+ this.greeting = greeting;
+ }
+
+ public int getPageSize() {
+ return pageSize;
+ }
+
+ /**
+ * Set a page size.
+ *
+ * {@link Value} is used to specify correct config key and default value
+ * in case configuration does not contain appropriate value.
+ * Original string value is mapped to target int using appropriate
+ * {@link io.helidon.config.ConfigMappers ConfigMapper}.
+ *
+ * @param pageSize page size
+ */
+ @Value(key = "page-size", withDefault = "10")
+ public void setPageSize(int pageSize) {
+ this.pageSize = pageSize;
+ }
+
+ public List getBasicRange() {
+ return basicRange;
+ }
+
+ /**
+ * Set a basic range.
+ *
+ * {@link Value} is used to specify correct config key and default value supplier
+ * in case configuration does not contain appropriate value.
+ * Supplier already returns default value in target type of a property.
+ *
+ * @param basicRange basic range
+ */
+ @Value(key = "basic-range", withDefaultSupplier = DefaultBasicRangeSupplier.class)
+ public void setBasicRange(List basicRange) {
+ this.basicRange = basicRange;
+ }
+
+ @Override
+ public String toString() {
+ return "AppConfig:\n"
+ + " greeting = " + greeting + "\n"
+ + " pageSize = " + pageSize + "\n"
+ + " basicRange= " + basicRange;
+ }
+
+ /**
+ * Supplier of default value for {@link #setBasicRange(List) basic-range} property.
+ */
+ public static class DefaultBasicRangeSupplier implements Supplier> {
+ @Override
+ public List get() {
+ return List.of(-10, 10);
+ }
+ }
+ }
+}
diff --git a/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/FactoryMethodExample.java b/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/FactoryMethodExample.java
new file mode 100644
index 000000000..a34faee50
--- /dev/null
+++ b/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/FactoryMethodExample.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.mapping;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+import io.helidon.config.objectmapping.Value;
+
+/**
+ * This example shows how to automatically deserialize configuration instance into POJO beans
+ * using factory method.
+ */
+public class FactoryMethodExample {
+
+ private FactoryMethodExample() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args arguments
+ */
+ public static void main(String... args) {
+ Config config = Config.create(ConfigSources.classpath("application.conf"));
+
+ AppConfig appConfig = config
+ // get "app" sub-node
+ .get("app")
+ // let config automatically deserialize the node to new AppConfig instance
+ .as(AppConfig.class)
+ .get();
+
+ System.out.println(appConfig);
+
+ // assert values loaded from application.conf
+
+ assert appConfig.getGreeting().equals("Hello");
+
+ assert appConfig.getPageSize() == 20;
+
+ assert appConfig.getBasicRange().size() == 2;
+ assert appConfig.getBasicRange().get(0) == -20;
+ assert appConfig.getBasicRange().get(1) == 20;
+ }
+
+ /**
+ * POJO representing an application configuration.
+ * Class is initialized from {@link Config} instance.
+ * During deserialization {@link #create(String, int, List) factory method} is invoked.
+ */
+ public static class AppConfig {
+ private final String greeting;
+ private final int pageSize;
+ private final List basicRange;
+
+ private AppConfig(String greeting, int pageSize, List basicRange) {
+ this.greeting = greeting;
+ this.pageSize = pageSize;
+ this.basicRange = basicRange;
+ }
+
+ public String getGreeting() {
+ return greeting;
+ }
+
+ public int getPageSize() {
+ return pageSize;
+ }
+
+ public List getBasicRange() {
+ return basicRange;
+ }
+
+ @Override
+ public String toString() {
+ return "AppConfig:\n"
+ + " greeting = " + greeting + "\n"
+ + " pageSize = " + pageSize + "\n"
+ + " basicRange= " + basicRange;
+ }
+
+ /**
+ * Creates new {@link AppConfig} instances.
+ *
+ * {@link Value} is used to specify config keys
+ * and default values in case configuration does not contain appropriate value.
+ *
+ * @param greeting greeting
+ * @param pageSize page size
+ * @param basicRange basic range
+ * @return new instance of {@link AppConfig}.
+ */
+ public static AppConfig create(@Value(key = "greeting", withDefault = "Hi")
+ String greeting,
+ @Value(key = "page-size", withDefault = "10")
+ int pageSize,
+ @Value(key = "basic-range", withDefaultSupplier = DefaultBasicRangeSupplier.class)
+ List basicRange) {
+ return new AppConfig(greeting, pageSize, basicRange);
+ }
+
+ /**
+ * Supplier of default value for {@code basic-range} property, see {@link #create(String, int, List)}.
+ */
+ public static class DefaultBasicRangeSupplier implements Supplier> {
+ @Override
+ public List get() {
+ return List.of(-10, 10);
+ }
+ }
+ }
+}
diff --git a/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/Main.java b/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/Main.java
new file mode 100644
index 000000000..730200638
--- /dev/null
+++ b/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/Main.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.mapping;
+
+/**
+ * Runs every example main class in this module/package.
+ */
+public class Main {
+
+ private Main() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args arguments
+ */
+ public static void main(String[] args) {
+ DeserializationExample.main(args);
+ FactoryMethodExample.main(args);
+ BuilderExample.main(args);
+ }
+
+}
diff --git a/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/package-info.java b/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/package-info.java
new file mode 100644
index 000000000..9c785ace4
--- /dev/null
+++ b/examples/config/mapping/src/main/java/io/helidon/examples/config/mapping/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The example shows how to use Config Mapping functionality.
+ */
+package io.helidon.examples.config.mapping;
diff --git a/examples/config/mapping/src/main/resources/application.conf b/examples/config/mapping/src/main/resources/application.conf
new file mode 100644
index 000000000..3699dcd83
--- /dev/null
+++ b/examples/config/mapping/src/main/resources/application.conf
@@ -0,0 +1,21 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+app {
+ greeting = "Hello"
+ page-size = 20
+ basic-range = [ -20, 20 ]
+}
diff --git a/examples/config/mapping/src/main/resources/logging.properties b/examples/config/mapping/src/main/resources/logging.properties
new file mode 100644
index 000000000..721cc5fc1
--- /dev/null
+++ b/examples/config/mapping/src/main/resources/logging.properties
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+handlers = java.util.logging.ConsoleHandler
+
+java.util.logging.ConsoleHandler.level = FINEST
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+java.util.logging.SimpleFormatter.format = [%1$tc] %4$s: %2$s - %5$s %6$s%n
+
+.level = INFO
+io.helidon.config.level = WARNING
+io.helidon.config.examples.level = FINEST
diff --git a/examples/config/metadata/README.md b/examples/config/metadata/README.md
new file mode 100644
index 000000000..7057c7213
--- /dev/null
+++ b/examples/config/metadata/README.md
@@ -0,0 +1,24 @@
+# Configuration metadata usage example
+
+This example reads configuration metadata from the classpath and creates a full
+`application.yaml` content with all possible configuration.
+
+## Setup
+
+This application does not have any root configuration on classpath, so it would generate an empty file.
+Add at least one module to `pom.xml` that can be configured to see the output.
+
+Example:
+```xml
+
+ io.helidon.security.providers
+ helidon-security-providers-oidc
+
+```
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-config-metadata.jar > application.yaml
+```
diff --git a/examples/config/metadata/pom.xml b/examples/config/metadata/pom.xml
new file mode 100644
index 000000000..a90c058ca
--- /dev/null
+++ b/examples/config/metadata/pom.xml
@@ -0,0 +1,66 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.config
+ helidon-examples-config-metadata
+ 1.0.0-SNAPSHOT
+ Helidon Examples Config Metadata
+
+
+ This example shows possibilities with configuration metadata. To test this, add a configurable library on the classpath
+ and run the example.
+
+
+
+ io.helidon.examples.config.metadata.ConfigMetadataMain
+
+
+
+
+ org.eclipse.parsson
+ parsson
+
+
+ io.helidon.config
+ helidon-config-metadata
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/config/metadata/src/main/java/io/helidon/examples/config/metadata/ConfigMetadataMain.java b/examples/config/metadata/src/main/java/io/helidon/examples/config/metadata/ConfigMetadataMain.java
new file mode 100644
index 000000000..8fd062242
--- /dev/null
+++ b/examples/config/metadata/src/main/java/io/helidon/examples/config/metadata/ConfigMetadataMain.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.metadata;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import io.helidon.config.metadata.ConfiguredOption;
+
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonReader;
+import jakarta.json.JsonReaderFactory;
+import jakarta.json.JsonValue;
+
+/**
+ * Reads configuration metadata and prints a full configuration example.
+ */
+public final class ConfigMetadataMain {
+ private static final Map TYPED_VALUES = Map.of("java.lang.Integer",
+ new TypedValue("1"),
+ "java.lang.Boolean",
+ new TypedValue("true"),
+ "java.lang.Long",
+ new TypedValue("1000"),
+ "java.lang.Short",
+ new TypedValue("0"),
+ "java.lang.String",
+ new TypedValue("value", true),
+ "java.net.URI",
+ new TypedValue("https://www.example.net", true),
+ "java.lang.Class",
+ new TypedValue("net.example.SomeClass", true),
+ "java.time.ZoneId",
+ new TypedValue("UTC", true));
+
+ private ConfigMetadataMain() {
+ }
+
+ /**
+ * Start the example.
+ * @param args ignored
+ */
+ public static void main(String[] args) throws IOException {
+ JsonReaderFactory readerFactory = Json.createReaderFactory(Map.of());
+
+ Enumeration files = ConfigMetadataMain.class.getClassLoader().getResources("META-INF/helidon/config-metadata.json");
+ List configuredTypes = new LinkedList<>();
+ Map typesMap = new HashMap<>();
+
+ while (files.hasMoreElements()) {
+ URL url = files.nextElement();
+ try (InputStream is = url.openStream()) {
+ JsonReader reader = readerFactory.createReader(is, StandardCharsets.UTF_8);
+ processMetadataJson(configuredTypes, typesMap, reader.readArray());
+ }
+ }
+ for (ConfiguredType configuredType : configuredTypes) {
+ if (configuredType.standalone()) {
+ printType(configuredType, typesMap);
+ }
+ }
+ }
+
+ private static void printType(ConfiguredType configuredType, Map typesMap) {
+ String prefix = configuredType.prefix();
+ System.out.println("# " + configuredType.description());
+ System.out.println("# " + configuredType.targetClass());
+ System.out.println(prefix + ":");
+ printType(configuredType, typesMap, 1, false);
+ }
+
+ private static void printType(ConfiguredType configuredType,
+ Map typesMap,
+ int nesting,
+ boolean listStart) {
+ String spaces = " ".repeat(nesting * 2);
+ Set properties = configuredType.properties();
+ boolean isListStart = listStart;
+
+ for (ConfiguredType.ConfiguredProperty property : properties) {
+ if (property.key() != null && property.key().contains(".*.")) {
+ // this is a nested key, must be resolved by the parent list node
+ continue;
+ }
+
+ printProperty(property, properties, typesMap, nesting, isListStart);
+ isListStart = false;
+ }
+
+ List inherited = configuredType.inherited();
+ for (String inheritedTypeName : inherited) {
+ ConfiguredType inheritedType = typesMap.get(inheritedTypeName);
+ if (inheritedType == null) {
+ System.out.println(spaces + "# Missing inherited type: " + inheritedTypeName);
+ } else {
+ printType(inheritedType, typesMap, nesting, false);
+ }
+ }
+ }
+
+ private static void printProperty(ConfiguredType.ConfiguredProperty property,
+ Set properties,
+ Map typesMap,
+ int nesting,
+ boolean listStart) {
+ String spaces = " ".repeat(nesting * 2);
+
+ printDocs(property, spaces, listStart);
+
+ if (property.kind() == ConfiguredOption.Kind.LIST) {
+ printListProperty(property, properties, typesMap, nesting, spaces);
+ return;
+ }
+
+ if (property.kind() == ConfiguredOption.Kind.MAP) {
+ printMapProperty(property, typesMap, nesting, spaces);
+ return;
+ }
+
+ TypedValue typed = TYPED_VALUES.get(property.type());
+ if (typed == null) {
+ // this is a nested type, or a missing type
+ ConfiguredType nestedType = typesMap.get(property.type());
+ if (nestedType == null) {
+ // either we have a list of allowed values, default value, or this is really a missing type
+ printAllowedValuesOrMissing(property, typesMap, nesting, spaces);
+ } else {
+ // proper nested type
+ if (property.merge()) {
+ printType(nestedType, typesMap, nesting, false);
+ } else {
+ System.out.println(spaces + property.outputKey() + ":");
+ printType(nestedType, typesMap, nesting + 1, false);
+ }
+ }
+ } else {
+ // this is a "leaf" node
+ if (property.defaultValue() == null) {
+ System.out.println(spaces + "# Generated value (property does not have a configured default)");
+ }
+ System.out.println(spaces + property.outputKey() + ": " + toTypedValue(property, typed));
+ }
+ }
+
+ private static void printMapProperty(ConfiguredType.ConfiguredProperty property,
+ Map typesMap,
+ int nesting,
+ String spaces) {
+ System.out.print(spaces);
+ System.out.println(property.outputKey() + ":");
+ TypedValue typedValue = TYPED_VALUES.get(property.type());
+
+ String mySpaces = " ".repeat((nesting + 1) * 2);
+ if (typedValue == null) {
+ System.out.println(mySpaces + "key: \"Unsupported map value type: " + property.type() + "\"");
+ } else {
+ System.out.println(mySpaces + "key-1: " + output(typedValue, typedValue.defaultsDefault()));
+ System.out.println(mySpaces + "key-2: " + output(typedValue, typedValue.defaultsDefault()));
+ }
+ }
+
+ private static void printListProperty(ConfiguredType.ConfiguredProperty property,
+ Set properties,
+ Map typesMap,
+ int nesting,
+ String spaces) {
+ System.out.print(spaces);
+ System.out.print(property.outputKey() + ":");
+ ConfiguredType listType = typesMap.get(property.type());
+
+ if (listType == null) {
+ if (property.provider()) {
+ listFromProvider(property, typesMap, nesting, spaces);
+ } else {
+ listFromTypes(property, properties, typesMap, nesting, spaces);
+ }
+ } else {
+ System.out.println();
+ System.out.print(spaces + "- ");
+ printType(listType, typesMap, nesting + 1, true);
+ }
+ }
+
+ private static void listFromProvider(ConfiguredType.ConfiguredProperty property,
+ Map typesMap,
+ int nesting,
+ String spaces) {
+ // let's find all supported providers
+ List providers = new LinkedList<>();
+ for (ConfiguredType value : typesMap.values()) {
+ if (value.provides().contains(property.type())) {
+ providers.add(value);
+ }
+ }
+
+ System.out.println();
+ if (providers.isEmpty()) {
+ System.out.print(spaces + "- # There are no modules on classpath providing " + property.type());
+ return;
+ }
+ for (ConfiguredType provider : providers) {
+ System.out.print(spaces + "- ");
+ if (provider.prefix() != null) {
+ System.out.println("# " + provider.description());
+ System.out.println(spaces + " " + provider.prefix() + ":");
+ printType(provider, typesMap, nesting + 2, false);
+ } else {
+ printType(provider, typesMap, nesting + 1, true);
+ }
+ }
+ }
+
+ private static void fromProvider(ConfiguredType.ConfiguredProperty property,
+ Map typesMap,
+ int nesting) {
+ String spaces = " ".repeat(nesting + 1);
+ // let's find all supported providers
+ List providers = new LinkedList<>();
+ for (ConfiguredType value : typesMap.values()) {
+ if (value.provides().contains(property.type())) {
+ providers.add(value);
+ }
+ }
+
+ if (providers.isEmpty()) {
+ System.out.println(spaces + " # There are no modules on classpath providing " + property.type());
+ return;
+ }
+
+ for (ConfiguredType provider : providers) {
+ System.out.println(spaces + " # ****** Provider Configuration ******");
+ System.out.println(spaces + " # " + provider.description());
+ System.out.println(spaces + " # " + provider.targetClass());
+ System.out.println(spaces + " # ************************************");
+ if (provider.prefix() != null) {
+ System.out.println(spaces + " " + provider.prefix() + ":");
+ printType(provider, typesMap, nesting + 2, false);
+ } else {
+ printType(provider, typesMap, nesting + 1, false);
+ }
+ }
+ }
+
+ private static void listFromTypes(ConfiguredType.ConfiguredProperty property,
+ Set properties,
+ Map typesMap,
+ int nesting,
+ String spaces) {
+ // this may be a list defined in configuration itself (*)
+ String prefix = property.outputKey() + ".*.";
+ Map children = new HashMap<>();
+ for (ConfiguredType.ConfiguredProperty configuredProperty : properties) {
+ if (configuredProperty.outputKey().startsWith(prefix)) {
+ children.put(configuredProperty.outputKey().substring(prefix.length()), configuredProperty);
+ }
+ }
+ if (children.isEmpty()) {
+ // this may be an array of primitive types / String
+ TypedValue typedValue = TYPED_VALUES.get(property.type());
+ if (typedValue == null) {
+ List allowedValues = property.allowedValues();
+ if (allowedValues.isEmpty()) {
+ System.out.println();
+ System.out.println(spaces + "# Missing type: " + property.type());
+ } else {
+ System.out.println();
+ typedValue = new TypedValue("", true);
+ for (ConfiguredType.AllowedValue allowedValue : allowedValues) {
+ // # Description
+ // # This is the default value
+ // actual value
+ System.out.print(spaces + " - ");
+ String nextLinePrefix = spaces + " ";
+ boolean firstLine = true;
+
+ if (allowedValue.description() != null && !allowedValue.description().isBlank()) {
+ firstLine = false;
+ System.out.println("#" + allowedValue.description());
+ }
+ if (allowedValue.value().equals(property.defaultValue())) {
+ if (firstLine) {
+ firstLine = false;
+ } else {
+ System.out.print(nextLinePrefix);
+ }
+ System.out.println("# This is the default value");
+ }
+ if (!firstLine) {
+ System.out.print(nextLinePrefix);
+ }
+ System.out.println(output(typedValue, allowedValue.value()));
+ }
+ }
+ } else {
+ printArray(typedValue);
+ }
+ } else {
+ System.out.println();
+ System.out.print(spaces + "- ");
+ boolean listStart = true;
+ for (var entry : children.entrySet()) {
+ ConfiguredType.ConfiguredProperty element = entry.getValue();
+ // we must modify the key
+ element.key(entry.getKey());
+ printProperty(element, properties, typesMap, nesting + 1, listStart);
+ listStart = false;
+ }
+ }
+ }
+
+ private static void printDocs(ConfiguredType.ConfiguredProperty property, String spaces, boolean firstLineNoSpaces) {
+ String description = property.description();
+ description = (description == null || description.isBlank()) ? null : description;
+
+ // type
+ System.out.print((firstLineNoSpaces ? "" : spaces));
+ System.out.print("# ");
+ System.out.println(property.type());
+
+ // description
+ if (description != null) {
+ description = description.replace('\n', ' ');
+ System.out.print(spaces);
+ System.out.print("# ");
+ System.out.println(description);
+ }
+
+ // required
+ if (!property.optional()) {
+ System.out.print(spaces);
+ System.out.println("# *********** REQUIRED ***********");
+ }
+ }
+
+ private static void printArray(TypedValue typedValue) {
+ String element = output(typedValue, typedValue.defaultsDefault());
+ String toPrint = " [" + element + "," + element + "]";
+ System.out.println(toPrint);
+ }
+
+ private static String output(TypedValue typed, String value) {
+ if (typed.escaped()) {
+ return "\"" + value + "\"";
+ }
+ return value;
+ }
+
+ private static void printAllowedValuesOrMissing(ConfiguredType.ConfiguredProperty property,
+ Map typesMap,
+ int nesting, String spaces) {
+ if (property.provider()) {
+ System.out.println(spaces + property.outputKey() + ":");
+ fromProvider(property, typesMap, nesting);
+ return;
+ }
+
+ List allowedValues = property.allowedValues();
+ if (allowedValues.isEmpty()) {
+ if (property.defaultValue() == null) {
+ System.out.println(spaces + property.outputKey() + ": \"Missing nested type: " + property.type() + "\"");
+ } else {
+ System.out.println(spaces + property.outputKey() + ": " + toTypedValue(property,
+ new TypedValue(property.defaultValue(),
+ true)));
+ }
+ } else {
+ List values = allowedValues.stream()
+ .map(ConfiguredType.AllowedValue::value)
+ .collect(Collectors.toList());
+ for (ConfiguredType.AllowedValue allowedValue : allowedValues) {
+ System.out.println(spaces + "# " + allowedValue.value() + ": " + allowedValue.description()
+ .replace("\n", " "));
+ }
+ if (property.defaultValue() == null) {
+ System.out.println(spaces + property.outputKey() + ": \"One of: " + values + "\"");
+ } else {
+ System.out.println(spaces + property.outputKey() + ": \"" + property.defaultValue() + "\"");
+ }
+ }
+ }
+
+ private static String toTypedValue(ConfiguredType.ConfiguredProperty property,
+ TypedValue typed) {
+ String value = property.defaultValue();
+
+ if (value == null) {
+ value = typed.defaultsDefault;
+ }
+
+ return output(typed, value);
+ }
+
+ private static void processMetadataJson(List configuredTypes,
+ Map typesMap,
+ JsonArray jsonArray) {
+ for (JsonValue jsonValue : jsonArray) {
+ processTypeArray(configuredTypes, typesMap, jsonValue.asJsonObject().getJsonArray("types"));
+ }
+ }
+
+ private static void processTypeArray(List configuredTypes,
+ Map typesMap,
+ JsonArray jsonArray) {
+ if (jsonArray == null) {
+ return;
+ }
+ for (JsonValue jsonValue : jsonArray) {
+ JsonObject type = jsonValue.asJsonObject();
+ ConfiguredType configuredType = ConfiguredType.create(type);
+ configuredTypes.add(configuredType);
+ typesMap.put(configuredType.targetClass(), configuredType);
+ }
+ }
+
+ private static final class TypedValue {
+ private final String defaultsDefault;
+ private final boolean escaped;
+
+ private TypedValue(String defaultsDefault) {
+ this(defaultsDefault, false);
+ }
+
+ private TypedValue(String defaultsDefault, boolean escaped) {
+ this.defaultsDefault = defaultsDefault;
+ this.escaped = escaped;
+ }
+
+ String defaultsDefault() {
+ return defaultsDefault;
+ }
+
+ boolean escaped() {
+ return escaped;
+ }
+ }
+}
diff --git a/examples/config/metadata/src/main/java/io/helidon/examples/config/metadata/ConfiguredType.java b/examples/config/metadata/src/main/java/io/helidon/examples/config/metadata/ConfiguredType.java
new file mode 100644
index 000000000..a5b5730df
--- /dev/null
+++ b/examples/config/metadata/src/main/java/io/helidon/examples/config/metadata/ConfiguredType.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.metadata;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import io.helidon.config.metadata.ConfiguredOption;
+
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonString;
+import jakarta.json.JsonValue;
+
+final class ConfiguredType {
+ private final Set allProperties = new HashSet<>();
+ private final List producerMethods = new LinkedList<>();
+ /**
+ * The type that is built by a builder, or created using create method.
+ */
+ private final String targetClass;
+ private final boolean standalone;
+ private final String prefix;
+ private final String description;
+ private final List provides;
+ private final List inherited = new LinkedList<>();
+
+ ConfiguredType(String targetClass, boolean standalone, String prefix, String description, List provides) {
+ this.targetClass = targetClass;
+ this.standalone = standalone;
+ this.prefix = prefix;
+ this.description = description;
+ this.provides = provides;
+ }
+
+ private static String paramsToString(String[] params) {
+ String result = Arrays.toString(params);
+ if (result.startsWith("[") && result.endsWith("]")) {
+ return result.substring(1, result.length() - 1);
+ }
+ return result;
+ }
+
+ String description() {
+ return description;
+ }
+
+ static ConfiguredType create(JsonObject type) {
+ ConfiguredType ct = new ConfiguredType(
+ type.getString("type"),
+ type.getBoolean("standalone", false),
+ type.getString("prefix", null),
+ type.getString("description", null),
+ toList(type.getJsonArray("provides"))
+ );
+
+ List producers = toList(type.getJsonArray("producers"));
+ for (String producer : producers) {
+ ct.addProducer(ProducerMethod.parse(producer));
+ }
+ List inherits = toList(type.getJsonArray("inherits"));
+ for (String inherit : inherits) {
+ ct.addInherited(inherit);
+ }
+
+ JsonArray options = type.getJsonArray("options");
+ for (JsonValue option : options) {
+ ct.addProperty(ConfiguredProperty.create(option.asJsonObject()));
+ }
+
+ return ct;
+ }
+
+ private static List toList(JsonArray array) {
+ if (array == null) {
+ return List.of();
+ }
+ List result = new ArrayList<>(array.size());
+
+ for (JsonValue jsonValue : array) {
+ result.add(((JsonString) jsonValue).getString());
+ }
+
+ return result;
+ }
+
+ ConfiguredType addProducer(ProducerMethod producer) {
+ producerMethods.add(producer);
+ return this;
+ }
+
+ ConfiguredType addProperty(ConfiguredProperty property) {
+ allProperties.add(property);
+ return this;
+ }
+
+ List producers() {
+ return producerMethods;
+ }
+
+ Set properties() {
+ return allProperties;
+ }
+
+ String targetClass() {
+ return targetClass;
+ }
+
+ boolean standalone() {
+ return standalone;
+ }
+
+ String prefix() {
+ return prefix;
+ }
+
+ List provides() {
+ return provides;
+ }
+
+ @Override
+ public String toString() {
+ return targetClass;
+ }
+
+ void addInherited(String classOrIface) {
+ inherited.add(classOrIface);
+ }
+
+ List inherited() {
+ return inherited;
+ }
+
+ static final class ProducerMethod {
+ private final boolean isStatic;
+ private final String owningClass;
+ private final String methodName;
+ private final String[] methodParams;
+
+ ProducerMethod(boolean isStatic, String owningClass, String methodName, String[] methodParams) {
+ this.isStatic = isStatic;
+ this.owningClass = owningClass;
+ this.methodName = methodName;
+ this.methodParams = methodParams;
+ }
+
+ public static ProducerMethod parse(String producer) {
+ int methodSeparator = producer.indexOf('#');
+ String owningClass = producer.substring(0, methodSeparator);
+ int paramBraceStart = producer.indexOf('(', methodSeparator);
+ String methodName = producer.substring(methodSeparator + 1, paramBraceStart);
+ int paramBraceEnd = producer.indexOf(')', paramBraceStart);
+ String parameters = producer.substring(paramBraceStart + 1, paramBraceEnd);
+ String[] methodParams = parameters.split(",");
+
+ return new ProducerMethod(false,
+ owningClass,
+ methodName,
+ methodParams);
+ }
+
+ @Override
+ public String toString() {
+ return owningClass
+ + "#"
+ + methodName + "("
+ + paramsToString(methodParams) + ")";
+ }
+ }
+
+ static final class ConfiguredProperty {
+ private final String builderMethod;
+ private final String key;
+ private final String description;
+ private final String defaultValue;
+ private final String type;
+ private final boolean experimental;
+ private final boolean optional;
+ private final ConfiguredOption.Kind kind;
+ private final List allowedValues;
+ private final boolean provider;
+ private final boolean merge;
+
+ // if this is a nested type
+ private ConfiguredType configuredType;
+ private String outputKey;
+
+ ConfiguredProperty(String builderMethod,
+ String key,
+ String description,
+ String defaultValue,
+ String type,
+ boolean experimental,
+ boolean optional,
+ ConfiguredOption.Kind kind,
+ boolean provider,
+ boolean merge,
+ List allowedValues) {
+ this.builderMethod = builderMethod;
+ this.key = key;
+ this.description = description;
+ this.defaultValue = defaultValue;
+ this.type = type;
+ this.experimental = experimental;
+ this.optional = optional;
+ this.kind = kind;
+ this.allowedValues = allowedValues;
+ this.outputKey = key;
+ this.provider = provider;
+ this.merge = merge;
+ }
+
+ public static ConfiguredProperty create(JsonObject json) {
+ return new ConfiguredProperty(
+ json.getString("method", null),
+ json.getString("key", null),
+ json.getString("description"),
+ json.getString("defaultValue", null),
+ json.getString("type", "java.lang.String"),
+ json.getBoolean("experimental", false),
+ !json.getBoolean("required", false),
+ toKind(json.getString("kind", null)),
+ json.getBoolean("provider", false),
+ json.getBoolean("merge", false),
+ toAllowedValues(json.getJsonArray("allowedValues"))
+ );
+ }
+
+ private static ConfiguredOption.Kind toKind(String kind) {
+ if (kind == null) {
+ return ConfiguredOption.Kind.VALUE;
+ }
+ return ConfiguredOption.Kind.valueOf(kind);
+ }
+
+ List allowedValues() {
+ return allowedValues;
+ }
+
+ private static List toAllowedValues(JsonArray allowedValues) {
+ if (allowedValues == null) {
+ return List.of();
+ }
+ List result = new ArrayList<>(allowedValues.size());
+
+ for (JsonValue allowedValue : allowedValues) {
+ JsonObject json = allowedValue.asJsonObject();
+ result.add(new AllowedValue(json.getString("value"), json.getString("description", null)));
+ }
+
+ return result;
+ }
+
+ String builderMethod() {
+ return builderMethod;
+ }
+
+ String outputKey() {
+ return outputKey;
+ }
+
+ String key() {
+ return key;
+ }
+
+ void key(String key) {
+ this.outputKey = key;
+ }
+
+ String description() {
+ return description;
+ }
+
+ String defaultValue() {
+ return defaultValue;
+ }
+
+ String type() {
+ return type;
+ }
+
+ boolean experimental() {
+ return experimental;
+ }
+
+ boolean optional() {
+ return optional;
+ }
+
+ ConfiguredOption.Kind kind() {
+ return kind;
+ }
+
+ boolean merge() {
+ return merge;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ConfiguredProperty that = (ConfiguredProperty) o;
+ return key.equals(that.key);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(key);
+ }
+
+ @Override
+ public String toString() {
+ return key;
+ }
+
+ boolean provider() {
+ return provider;
+ }
+ }
+
+ static final class AllowedValue {
+ private final String value;
+ private final String description;
+
+ private AllowedValue(String value, String description) {
+ this.value = value;
+ this.description = description;
+ }
+
+ String value() {
+ return value;
+ }
+
+ String description() {
+ return description;
+ }
+ }
+}
diff --git a/examples/config/metadata/src/main/java/io/helidon/examples/config/metadata/package-info.java b/examples/config/metadata/src/main/java/io/helidon/examples/config/metadata/package-info.java
new file mode 100644
index 000000000..959e669f3
--- /dev/null
+++ b/examples/config/metadata/src/main/java/io/helidon/examples/config/metadata/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example of processing configuration metadata.
+ */
+package io.helidon.examples.config.metadata;
diff --git a/examples/config/overrides/README.md b/examples/config/overrides/README.md
new file mode 100644
index 000000000..966a6435d
--- /dev/null
+++ b/examples/config/overrides/README.md
@@ -0,0 +1,27 @@
+# Helidon Config Overrides Example
+
+This example shows how to load configuration from multiple
+configuration sources, specifically where one of the sources _overrides_ other
+sources.
+
+The application treats
+[`resources/application.yaml`](./src/main/resources/application.yaml) and
+[`conf/priority-config.yaml`](./conf/priority-config.yaml)
+as the config sources for the its configuration.
+
+The `application.yaml` file is packaged along with the application code, while
+the files in `conf` would be provided during deployment to tailor the behavior
+of the application to that specific environment.
+
+The application also loads
+[`conf/overrides.properties`](./conf/overrides.properties) but as an
+_override_ config
+source. This file contains key _expressions_ (including wildcards) and values which
+take precedence over the settings in the original config sources.
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-config-overrides.jar
+```
diff --git a/examples/config/overrides/conf/overrides.properties b/examples/config/overrides/conf/overrides.properties
new file mode 100644
index 000000000..3ff3dbc0a
--- /dev/null
+++ b/examples/config/overrides/conf/overrides.properties
@@ -0,0 +1,29 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Overrides provides central place for
+# managing app configuration across envs.
+
+# key format: $env.$pod...
+
+# override selected pod (abcdef) logging level
+prod.abcdef.logging.level = FINEST
+
+# "production" environment, any pod
+prod.*.logging.level = WARNING
+
+# "test" environment, any pod
+test.*.logging.level = FINE
diff --git a/examples/config/overrides/conf/priority-config.yaml b/examples/config/overrides/conf/priority-config.yaml
new file mode 100644
index 000000000..70b33f389
--- /dev/null
+++ b/examples/config/overrides/conf/priority-config.yaml
@@ -0,0 +1,29 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# set context
+
+env: prod
+pod: abcdef
+
+$env:
+ $pod:
+ logging:
+ level: ERROR
+
+ app:
+ greeting: Ahoy
+ page-size: 42
diff --git a/examples/config/overrides/pom.xml b/examples/config/overrides/pom.xml
new file mode 100644
index 000000000..1e8dd408b
--- /dev/null
+++ b/examples/config/overrides/pom.xml
@@ -0,0 +1,61 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.config
+ helidon-examples-config-overrides
+ 1.0.0-SNAPSHOT
+ Helidon Examples Config Overrides
+
+
+ The example shows how to use Overrides in Configuration API.
+
+
+
+ io.helidon.examples.config.overrides.Main
+
+
+
+
+ io.helidon.bundles
+ helidon-bundles-config
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/config/overrides/src/main/java/io/helidon/examples/config/overrides/Main.java b/examples/config/overrides/src/main/java/io/helidon/examples/config/overrides/Main.java
new file mode 100644
index 000000000..30a0b5e9c
--- /dev/null
+++ b/examples/config/overrides/src/main/java/io/helidon/examples/config/overrides/Main.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.overrides;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
+import io.helidon.config.Config;
+import io.helidon.config.OverrideSources;
+
+import static io.helidon.config.ConfigSources.classpath;
+import static io.helidon.config.ConfigSources.file;
+import static io.helidon.config.PollingStrategies.regular;
+
+/**
+ * Overrides example.
+ *
+ * Shows the Overrides feature where values from config sources might be overridden by override source.
+ *
+ * In this example, {@code application.yaml} is meant to be default application configuration distributed with an app, containing
+ * a wildcard configuration nodes representing the defaults for every environment and pod as well as a default definition of
+ * these values. The source {@code conf/priority-config.yaml} is a higher priority configuration source which can be in a real
+ * app dynamically changed (i.e. {@code Kubernetes ConfigMap} mapped as the file) and contains the current {@code env} and {@code
+ * pod} values ({@code prod} and {@code abcdef} in this example) and higher priority default configuration. So far the current
+ * configuration looks like this:
+ *
+ * prod:
+ * abcdef:
+ * logging:
+ * level: ERROR
+ * app:
+ * greeting: Ahoy
+ * page-size: 42
+ * basic-range:
+ * - -20
+ * - 20
+ *
+ * But the override source just overrides values for environment: {@code prod} and pod: {@code abcdef} (it is the first
+ * overriding rule found) and value for key {@code prod.abcdef.logging.level = FINEST}. For completeness, we would say that the
+ * other pods in {@code prod} environment has overridden config value {@code prod.*.logging.level} to {@code WARNING} and all
+ * pods
+ * {@code test.*.logging.level} to {@code FINE}.
+ */
+public final class Main {
+
+ private Main() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args arguments
+ * @throws InterruptedException when a sleeper awakes
+ */
+ public static void main(String... args) throws InterruptedException {
+ Config config = Config
+ .builder()
+ // specify config sources
+ .sources(file("conf/priority-config.yaml").pollingStrategy(regular(Duration.ofSeconds(1))),
+ classpath("application.yaml"))
+ // specify overrides source
+ .overrides(OverrideSources.file("conf/overrides.properties")
+ .pollingStrategy(regular(Duration.ofSeconds(1))))
+ .build();
+
+ // Resolve current runtime context
+ String env = config.get("env").asString().get();
+ String pod = config.get("pod").asString().get();
+
+ // get logging config for the current runtime
+ Config loggingConfig = config
+ .get(env)
+ .get(pod)
+ .get("logging");
+
+ // initialize logging from config
+ initLogging(loggingConfig);
+
+ // react on changes of logging configuration
+ loggingConfig.onChange(Main::initLogging);
+
+ TimeUnit.MINUTES.sleep(1);
+ }
+
+ /**
+ * Initialize logging from config.
+ */
+ private static void initLogging(Config loggingConfig) {
+ String level = loggingConfig.get("level").asString().orElse("WARNING");
+ //e.g. initialize logging using configured level...
+
+ System.out.println("Set logging level to " + level + ".");
+ }
+
+}
diff --git a/examples/config/overrides/src/main/java/io/helidon/examples/config/overrides/package-info.java b/examples/config/overrides/src/main/java/io/helidon/examples/config/overrides/package-info.java
new file mode 100644
index 000000000..9a3f5877f
--- /dev/null
+++ b/examples/config/overrides/src/main/java/io/helidon/examples/config/overrides/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The example shows how to use Overrides in Configuration API.
+ */
+package io.helidon.examples.config.overrides;
diff --git a/examples/config/overrides/src/main/resources/application.yaml b/examples/config/overrides/src/main/resources/application.yaml
new file mode 100644
index 000000000..b13e7ad01
--- /dev/null
+++ b/examples/config/overrides/src/main/resources/application.yaml
@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# default context
+
+env: dev
+pod: local
+
+# app config
+# key format: $env.$pod...
+
+$env:
+ $pod:
+ logging:
+ level: CONFIG
+
+ app:
+ greeting: Hello
+ page-size: 20
+ basic-range:
+ - -20
+ - 20
diff --git a/examples/config/overrides/src/main/resources/logging.properties b/examples/config/overrides/src/main/resources/logging.properties
new file mode 100644
index 000000000..721cc5fc1
--- /dev/null
+++ b/examples/config/overrides/src/main/resources/logging.properties
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+handlers = java.util.logging.ConsoleHandler
+
+java.util.logging.ConsoleHandler.level = FINEST
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+java.util.logging.SimpleFormatter.format = [%1$tc] %4$s: %2$s - %5$s %6$s%n
+
+.level = INFO
+io.helidon.config.level = WARNING
+io.helidon.config.examples.level = FINEST
diff --git a/examples/config/pom.xml b/examples/config/pom.xml
new file mode 100644
index 000000000..6f416e447
--- /dev/null
+++ b/examples/config/pom.xml
@@ -0,0 +1,45 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples
+ helidon-examples-project
+ 1.0.0-SNAPSHOT
+
+ io.helidon.examples.config
+ helidon-examples-config-project
+ 1.0.0-SNAPSHOT
+ pom
+ Helidon Examples Config
+
+
+ basics
+ changes
+ git
+ mapping
+ overrides
+ sources
+ profiles
+ metadata
+
+
+
diff --git a/examples/config/profiles/README.md b/examples/config/profiles/README.md
new file mode 100644
index 000000000..8c98e3308
--- /dev/null
+++ b/examples/config/profiles/README.md
@@ -0,0 +1,31 @@
+# Helidon Config Profiles Example
+
+This example shows how to load configuration from multiple
+configuration sources using profiles.
+
+This example contains the following profiles:
+
+1. no profile - if you start the application with no profile, the usual `src/main/resources/application.yaml` will be used
+2. `local` - `src/main/resources/application-local.yaml` will be used
+3. `dev` - has an explicit profile file `config-profile-dev.yaml` on classpath that defines an inlined configuration
+4. `stage` - has an explicit profile file `config-profile-stage.yaml` on classpath that defines a classpath config source
+4. `prod` - has an explicit profile file `config-profile-prod.yaml` on file system that defines a path config source
+
+To switch profiles
+- either use a system property `config.profile`
+- or use an environment variable `HELIDON_CONFIG_PROFILE`
+
+
+## How to run this example:
+
+Build the application
+```shell
+mvn clean package
+```
+
+Run it with a profile
+```shell
+java -Dconfig.profile=prod -jar target/helidon-examples-config-profiles.jar
+```
+
+Changing the profile name should use different configuration.
\ No newline at end of file
diff --git a/examples/config/profiles/config-profile-prod.yaml b/examples/config/profiles/config-profile-prod.yaml
new file mode 100644
index 000000000..fdf11d8d9
--- /dev/null
+++ b/examples/config/profiles/config-profile-prod.yaml
@@ -0,0 +1,22 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+sources:
+ - type: "environment-variables"
+ - type: "system-properties"
+ - type: "file"
+ properties:
+ path: "config/config-prod.yaml"
diff --git a/examples/config/profiles/config/config-prod.yaml b/examples/config/profiles/config/config-prod.yaml
new file mode 100644
index 000000000..f75b5e67c
--- /dev/null
+++ b/examples/config/profiles/config/config-prod.yaml
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+message: "config/config-prod.yaml"
diff --git a/examples/config/profiles/pom.xml b/examples/config/profiles/pom.xml
new file mode 100644
index 000000000..318453675
--- /dev/null
+++ b/examples/config/profiles/pom.xml
@@ -0,0 +1,65 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.config
+ helidon-examples-config-profiles
+ 1.0.0-SNAPSHOT
+ Helidon Examples Config Profiles
+
+
+ The example shows how to use Config Profiles (meta configuration).
+
+
+
+ io.helidon.examples.config.profiles.Main
+
+
+
+
+ io.helidon.config
+ helidon-config
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/config/profiles/src/main/java/io/helidon/examples/config/profiles/Main.java b/examples/config/profiles/src/main/java/io/helidon/examples/config/profiles/Main.java
new file mode 100644
index 000000000..f9531db1b
--- /dev/null
+++ b/examples/config/profiles/src/main/java/io/helidon/examples/config/profiles/Main.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.profiles;
+
+import io.helidon.config.Config;
+
+/**
+ * Example main class.
+ */
+public final class Main {
+ private Main() {
+ }
+
+ /**
+ * Main method.
+ *
+ * @param args ignored
+ */
+ public static void main(String[] args) {
+ Config config = Config.create();
+
+ System.out.println("Configured message: " + config.get("message").asString().orElse("MISSING"));
+ }
+}
diff --git a/examples/config/profiles/src/main/java/io/helidon/examples/config/profiles/package-info.java b/examples/config/profiles/src/main/java/io/helidon/examples/config/profiles/package-info.java
new file mode 100644
index 000000000..e00866372
--- /dev/null
+++ b/examples/config/profiles/src/main/java/io/helidon/examples/config/profiles/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example showing usage of configuration profiles in Helidon SE.
+ */
+package io.helidon.examples.config.profiles;
diff --git a/examples/config/profiles/src/main/resources/application-local.yaml b/examples/config/profiles/src/main/resources/application-local.yaml
new file mode 100644
index 000000000..7bf6500e9
--- /dev/null
+++ b/examples/config/profiles/src/main/resources/application-local.yaml
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+message: "src/main/resources/application-local.yaml"
diff --git a/examples/config/profiles/src/main/resources/application-stage.yaml b/examples/config/profiles/src/main/resources/application-stage.yaml
new file mode 100644
index 000000000..ab0057ee3
--- /dev/null
+++ b/examples/config/profiles/src/main/resources/application-stage.yaml
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+message: "src/main/resources/application-stage.yaml"
diff --git a/examples/config/profiles/src/main/resources/application.yaml b/examples/config/profiles/src/main/resources/application.yaml
new file mode 100644
index 000000000..b1a9a1238
--- /dev/null
+++ b/examples/config/profiles/src/main/resources/application.yaml
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+message: "src/main/resources/application.yaml"
diff --git a/examples/config/profiles/src/main/resources/config-profile-dev.yaml b/examples/config/profiles/src/main/resources/config-profile-dev.yaml
new file mode 100644
index 000000000..74e7e903b
--- /dev/null
+++ b/examples/config/profiles/src/main/resources/config-profile-dev.yaml
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+sources:
+ - type: "inlined"
+ properties:
+ message: "inlined in dev profile"
diff --git a/examples/config/profiles/src/main/resources/config-profile-stage.yaml b/examples/config/profiles/src/main/resources/config-profile-stage.yaml
new file mode 100644
index 000000000..bbc285948
--- /dev/null
+++ b/examples/config/profiles/src/main/resources/config-profile-stage.yaml
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+sources:
+ - type: "classpath"
+ properties:
+ resource: "application-stage.yaml"
diff --git a/examples/config/sources/README.md b/examples/config/sources/README.md
new file mode 100644
index 000000000..cfb5c3210
--- /dev/null
+++ b/examples/config/sources/README.md
@@ -0,0 +1,23 @@
+# Helidon Config Sources Example
+
+This example shows how to load configuration from multiple
+configuration sources.
+
+1. [`DirectorySourceExample.java`](src/main/java/io/helidon/examples/config/sources/DirectorySourceExample.java)
+reads configuration from multiple files in a directory by specifying only the directory.
+2. [`LoadSourcesExample.java`](src/main/java/io/helidon/examples/config/sources/LoadSourcesExample.java)
+uses _meta-configuration_ files [`conf/meta-config.yaml`](./conf/meta-config.yaml)
+and [`src/main/resources/meta-config.yaml`](./src/main/resources/meta-config.yaml)
+which contain not the configuration itself but
+_instructions for loading_ the configuration: what type, from where, etc. It also
+applies a filter to modify config values whose keys match a certain pattern.
+3. [`WithSourcesExample.java`](src/main/java/io/helidon/examples/config/sources/WithSourcesExample.java)
+combines multiple config sources into a single configuration instance (and adds a
+filter.
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-config-sources.jar
+```
diff --git a/examples/config/sources/conf/config.yaml b/examples/config/sources/conf/config.yaml
new file mode 100644
index 000000000..4b08deacc
--- /dev/null
+++ b/examples/config/sources/conf/config.yaml
@@ -0,0 +1,25 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# In config.yaml we are going to override default configuration.
+# It is supposed to be deployment dependent configuration.
+
+meta:
+ env: PROD
+
+app:
+ page-size: 10
+
diff --git a/examples/config/sources/conf/dev.yaml b/examples/config/sources/conf/dev.yaml
new file mode 100644
index 000000000..312f8d84c
--- /dev/null
+++ b/examples/config/sources/conf/dev.yaml
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# In dev.yaml we are going to override all lower layer configuration files.
+# It is supposed to be development specific configuration.
+
+# IT SHOULD NOT BE PLACED IN VCS. EACH DEVELOPER CAN CUSTOMIZE THE FILE AS NEEDED.
+# I.e. for example with GIT place following line into .gitignore file:
+#*/conf/dev.yaml
+
+meta:
+ env: DEV
+
+component:
+ audit:
+ logging:
+ level: fine
diff --git a/examples/config/sources/conf/meta-config.yaml b/examples/config/sources/conf/meta-config.yaml
new file mode 100644
index 000000000..31e31fe2d
--- /dev/null
+++ b/examples/config/sources/conf/meta-config.yaml
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+sources:
+ - type: "environment-variables"
+ - type: "system-properties"
+ - type: "file"
+ properties:
+ path: "conf/dev.yaml"
+ optional: true
+ - type: "file"
+ properties:
+ path: "conf/config.yaml"
+ optional: true
+ - type: "classpath"
+ properties:
+ resource: "default.yaml"
diff --git a/examples/config/sources/conf/secrets/password b/examples/config/sources/conf/secrets/password
new file mode 100644
index 000000000..5bbaf8758
--- /dev/null
+++ b/examples/config/sources/conf/secrets/password
@@ -0,0 +1 @@
+changeit
\ No newline at end of file
diff --git a/examples/config/sources/conf/secrets/username b/examples/config/sources/conf/secrets/username
new file mode 100644
index 000000000..1ce97e36c
--- /dev/null
+++ b/examples/config/sources/conf/secrets/username
@@ -0,0 +1 @@
+libor
\ No newline at end of file
diff --git a/examples/config/sources/pom.xml b/examples/config/sources/pom.xml
new file mode 100644
index 000000000..147edb88c
--- /dev/null
+++ b/examples/config/sources/pom.xml
@@ -0,0 +1,65 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.config
+ helidon-examples-config-sources
+ 1.0.0-SNAPSHOT
+ Helidon Examples Config Sources
+
+
+ This example shows how to merge the configuration from different sources.
+
+
+
+ io.helidon.examples.config.sources.Main
+
+
+
+
+ io.helidon.config
+ helidon-config
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/config/sources/src/main/java/io/helidon/examples/config/sources/DirectorySourceExample.java b/examples/config/sources/src/main/java/io/helidon/examples/config/sources/DirectorySourceExample.java
new file mode 100644
index 000000000..226c9e349
--- /dev/null
+++ b/examples/config/sources/src/main/java/io/helidon/examples/config/sources/DirectorySourceExample.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.sources;
+
+import io.helidon.config.Config;
+
+import static io.helidon.config.ConfigSources.directory;
+
+/**
+ * This example shows how to read configuration from several files placed in selected directory.
+ */
+public class DirectorySourceExample {
+
+ private DirectorySourceExample() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args arguments
+ */
+ public static void main(String... args) {
+ /*
+ Creates a config from files from specified directory.
+ E.g. Kubernetes Secrets:
+ */
+
+ Config secrets = Config.builder(directory("conf/secrets"))
+ .disableEnvironmentVariablesSource()
+ .disableSystemPropertiesSource()
+ .build();
+
+ String username = secrets.get("username").asString().get();
+ System.out.println("Username: " + username);
+ assert username.equals("libor");
+
+ String password = secrets.get("password").asString().get();
+ System.out.println("Password: " + password);
+ assert password.equals("changeit");
+ }
+
+}
diff --git a/examples/config/sources/src/main/java/io/helidon/examples/config/sources/LoadSourcesExample.java b/examples/config/sources/src/main/java/io/helidon/examples/config/sources/LoadSourcesExample.java
new file mode 100644
index 000000000..472cb7810
--- /dev/null
+++ b/examples/config/sources/src/main/java/io/helidon/examples/config/sources/LoadSourcesExample.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.sources;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigValue;
+
+import static io.helidon.config.ConfigSources.classpath;
+import static io.helidon.config.ConfigSources.file;
+
+/**
+ * This example shows how to merge the configuration from different sources
+ * loaded from meta configuration.
+ *
+ * @see WithSourcesExample
+ */
+public class LoadSourcesExample {
+
+ private LoadSourcesExample() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args arguments
+ */
+ public static void main(String... args) {
+ /*
+ Creates a configuration from list of config sources loaded from meta sources:
+ - conf/meta-config.yaml - - deployment dependent meta-config file, loaded from file on filesystem;
+ - meta-config.yaml - application default meta-config file, loaded form classpath;
+ with a filter which convert values with keys ending with "level" to upper case
+ */
+
+ Config metaConfig = Config.create(file("conf/meta-config.yaml").optional(),
+ classpath("meta-config.yaml"));
+
+ Config config = Config.builder()
+ .config(metaConfig)
+ .addFilter((key, stringValue) -> key.name().equals("level") ? stringValue.toUpperCase() : stringValue)
+ .build();
+
+ // Optional environment type, from dev.yaml:
+ ConfigValue env = config.get("meta.env").asString();
+ env.ifPresent(e -> System.out.println("Environment: " + e));
+ assert env.get().equals("DEV");
+
+ // Default value (default.yaml): Config Sources Example
+ String appName = config.get("app.name").asString().get();
+ System.out.println("Name: " + appName);
+ assert appName.equals("Config Sources Example");
+
+ // Page size, from config.yaml: 10
+ int pageSize = config.get("app.page-size").asInt().get();
+ System.out.println("Page size: " + pageSize);
+ assert pageSize == 10;
+
+ // Applied filter (uppercase logging level), from dev.yaml: finest -> FINEST
+ String level = config.get("component.audit.logging.level").asString().get();
+ System.out.println("Level: " + level);
+ assert level.equals("FINE");
+ }
+
+}
diff --git a/examples/config/sources/src/main/java/io/helidon/examples/config/sources/Main.java b/examples/config/sources/src/main/java/io/helidon/examples/config/sources/Main.java
new file mode 100644
index 000000000..a0c1be3e0
--- /dev/null
+++ b/examples/config/sources/src/main/java/io/helidon/examples/config/sources/Main.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.sources;
+
+/**
+ * Runs every example main class in this module/package.
+ */
+public class Main {
+
+ private Main() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args arguments
+ */
+ public static void main(String[] args) {
+ WithSourcesExample.main(args);
+ LoadSourcesExample.main(args);
+ DirectorySourceExample.main(args);
+ }
+
+}
diff --git a/examples/config/sources/src/main/java/io/helidon/examples/config/sources/WithSourcesExample.java b/examples/config/sources/src/main/java/io/helidon/examples/config/sources/WithSourcesExample.java
new file mode 100644
index 000000000..96c0adce8
--- /dev/null
+++ b/examples/config/sources/src/main/java/io/helidon/examples/config/sources/WithSourcesExample.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.config.sources;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigValue;
+
+import static io.helidon.config.ConfigSources.classpath;
+import static io.helidon.config.ConfigSources.file;
+
+/**
+ * This example shows how to merge the configuration from different sources.
+ *
+ * @see LoadSourcesExample
+ */
+public class WithSourcesExample {
+
+ private WithSourcesExample() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args arguments
+ */
+ public static void main(String... args) {
+ /*
+ Creates a config source composed of following sources:
+ - conf/dev.yaml - developer specific configuration, should not be placed in VCS;
+ - conf/config.yaml - deployment dependent configuration, for example prod, stage, etc;
+ - default.yaml - application default values, loaded form classpath;
+ with a filter which convert values with keys ending with "level" to upper case
+ */
+
+ Config config = Config
+ .builder(file("conf/dev.yaml").optional(),
+ file("conf/config.yaml").optional(),
+ classpath("default.yaml"))
+ .addFilter((key, stringValue) -> key.name().equals("level") ? stringValue.toUpperCase() : stringValue)
+ .build();
+
+ // Environment type, from dev.yaml:
+ ConfigValue env = config.get("meta.env").asString();
+ env.ifPresent(e -> System.out.println("Environment: " + e));
+ assert env.get().equals("DEV");
+
+ // Default value (default.yaml): Config Sources Example
+ String appName = config.get("app.name").asString().get();
+ System.out.println("Name: " + appName);
+ assert appName.equals("Config Sources Example");
+
+ // Page size, from config.yaml: 10
+ int pageSize = config.get("app.page-size").asInt().get();
+ System.out.println("Page size: " + pageSize);
+ assert pageSize == 10;
+
+ // Applied filter (uppercase logging level), from dev.yaml: finest -> FINEST
+ String level = config.get("component.audit.logging.level").asString().get();
+ System.out.println("Level: " + level);
+ assert level.equals("FINE");
+ }
+
+}
diff --git a/examples/config/sources/src/main/java/io/helidon/examples/config/sources/package-info.java b/examples/config/sources/src/main/java/io/helidon/examples/config/sources/package-info.java
new file mode 100644
index 000000000..a9d4530f2
--- /dev/null
+++ b/examples/config/sources/src/main/java/io/helidon/examples/config/sources/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 example shows how to merge the configuration from different sources.
+ */
+package io.helidon.examples.config.sources;
diff --git a/examples/config/sources/src/main/resources/default.yaml b/examples/config/sources/src/main/resources/default.yaml
new file mode 100644
index 000000000..b1dfac982
--- /dev/null
+++ b/examples/config/sources/src/main/resources/default.yaml
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# In default.yaml we are going to configure application default values.
+# It is expected it is overridden in:
+# - conf/config.yaml in production environment
+# - conf/dev.yaml by developer on local machine
+
+app:
+ name: Config Sources Example
+ page-size: 20
+
+component:
+ audit:
+ logging:
+ level: info
+ monitoring:
+ logging:
+ level: warning
diff --git a/examples/config/sources/src/main/resources/logging.properties b/examples/config/sources/src/main/resources/logging.properties
new file mode 100644
index 000000000..721cc5fc1
--- /dev/null
+++ b/examples/config/sources/src/main/resources/logging.properties
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+handlers = java.util.logging.ConsoleHandler
+
+java.util.logging.ConsoleHandler.level = FINEST
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+java.util.logging.SimpleFormatter.format = [%1$tc] %4$s: %2$s - %5$s %6$s%n
+
+.level = INFO
+io.helidon.config.level = WARNING
+io.helidon.config.examples.level = FINEST
diff --git a/examples/config/sources/src/main/resources/meta-config.yaml b/examples/config/sources/src/main/resources/meta-config.yaml
new file mode 100644
index 000000000..c0b1365f0
--- /dev/null
+++ b/examples/config/sources/src/main/resources/meta-config.yaml
@@ -0,0 +1,21 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# ordered list of sources
+sources:
+ - type: "classpath"
+ properties:
+ resource: "default.yaml"
diff --git a/examples/config/sources/src/main/resources/overrides.properties b/examples/config/sources/src/main/resources/overrides.properties
new file mode 100644
index 000000000..a2046d7a1
--- /dev/null
+++ b/examples/config/sources/src/main/resources/overrides.properties
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+component.*.logging.level = finest
diff --git a/examples/cors/README.md b/examples/cors/README.md
new file mode 100644
index 000000000..54517d949
--- /dev/null
+++ b/examples/cors/README.md
@@ -0,0 +1,197 @@
+
+# Helidon SE CORS Example
+
+This example shows a simple greeting application, similar to the one from the
+Helidon SE QuickStart, enhanced with CORS support.
+
+Look at the `resources/application.yaml` file and notice the `restrictive-cors`
+section. (We'll come back to the `cors` section later.) The `Main#corsSupportForGreeting` method loads this
+configuration and uses it to set up CORS for the application's endpoints.
+
+Near the end of the `resources/logging.properties` file, a commented line would turn on `FINE
+` logging that would reveal how the Helidon CORS support makes it decisions. To see that logging,
+uncomment that line and then package and run the application.
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-cors.jar
+```
+
+## Using the app endpoints as with the "classic" greeting app
+
+These normal greeting app endpoints work just as in the original greeting app:
+
+```shell
+curl -X GET http://localhost:8080/greet
+#{"message":"Hello World!"}
+
+curl -X GET http://localhost:8080/greet/Joe
+#{"message":"Hello Joe!"}
+
+curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Hola"}' http://localhost:8080/greet/greeting
+
+curl -X GET http://localhost:8080/greet/Jose
+#{"message":"Hola Jose!"}
+```
+
+## Using CORS
+
+### Sending "simple" CORS requests
+
+The following requests illustrate the CORS protocol with the example app.
+
+By setting `Origin` and `Host` headers that do not indicate the same system we trigger CORS processing in the
+ server:
+
+```shell
+# Follow the CORS protocol for GET
+curl -i -X GET -H "Origin: http://foo.com" -H "Host: here.com" http://localhost:8080/greet
+
+```
+```text
+HTTP/1.1 200 OK
+Access-Control-Allow-Origin: *
+Content-Type: application/json
+Date: Thu, 30 Apr 2020 17:25:51 -0500
+Vary: Origin
+connection: keep-alive
+content-length: 27
+
+{"greeting":"Hola World!"}
+```
+Note the new headers `Access-Control-Allow-Origin` and `Vary` in the response.
+
+The same happens for a `GET` requesting a personalized greeting (by passing the name of the
+ person to be greeted):
+```shell
+curl -i -X GET -H "Origin: http://foo.com" -H "Host: here.com" http://localhost:8080/greet/Joe
+
+```
+```text
+HTTP/1.1 200 OK
+Date: Wed, 31 Jan 2024 11:58:06 +0100
+Access-Control-Allow-Origin: *
+Connection: keep-alive
+Content-Length: 24
+Content-Type: application/json
+Vary: ORIGIN
+
+{"greeting":"Hola Joe!"}
+```
+These two `GET` requests work because the `Main#corsSupportForGreeting` method adds a default `CrossOriginConfig` to the
+`CorsSupport.Builder` it sets up. This is in addition to adding a `CrossOriginConfig` based on the `restrictive-cors`
+configuration in `application.yaml` we looked at earlier.
+
+These are what CORS calls "simple" requests; the CORS protocol for these adds headers to the request and response which
+the client and server exchange anyway.
+
+### "Non-simple" CORS requests
+
+The CORS protocol requires the client to send a _pre-flight_ request before sending a request
+ that changes state on the server, such as `PUT` or `DELETE`, and to check the returned status
+ and headers to make sure the server is willing to accept the actual request. CORS refers to such `PUT` and `DELETE`
+ requests as "non-simple" ones.
+
+This command sends a pre-flight `OPTIONS` request to see if the server will accept a subsequent `PUT` request from the
+specified origin to change the greeting:
+```shell
+curl -i -X OPTIONS \
+ -H "Access-Control-Request-Method: PUT" \
+ -H "Origin: http://foo.com" \
+ -H "Host: here.com" \
+ http://localhost:8080/greet/greeting
+```
+```text
+HTTP/1.1 200 OK
+Date: Wed, 31 Jan 2024 12:00:15 +0100
+Access-Control-Allow-Methods: PUT
+Access-Control-Allow-Origin: http://foo.com
+Access-Control-Max-Age: 3600
+Connection: keep-alive
+Content-Length: 0
+```
+The successful status and the returned `Access-Control-Allow-xxx` headers indicate that the
+ server accepted the pre-flight request. That means it is OK for us to send `PUT` request to perform the actual change
+ of greeting. (See below for how the server rejects a pre-flight request.)
+```shell
+curl -i -X PUT \
+ -H "Origin: http://foo.com" \
+ -H "Host: here.com" \
+ -H "Access-Control-Allow-Methods: PUT" \
+ -H "Access-Control-Allow-Origin: http://foo.com" \
+ -H "Content-Type: application/json" \
+ -d "{ \"greeting\" : \"Cheers\" }" \
+ http://localhost:8080/greet/greeting
+```
+```text
+HTTP/1.1 204 No Content
+Date: Wed, 31 Jan 2024 12:01:45 +0100
+Access-Control-Allow-Origin: http://foo.com
+Connection: keep-alive
+Content-Length: 0
+Vary: ORIGIN
+```
+And we run one more `GET` to observe the change in the greeting:
+```shell
+curl -i -X GET -H "Origin: http://foo.com" -H "Host: here.com" http://localhost:8080/greet/Joe
+```
+```text
+HTTP/1.1 200 OK
+Date: Wed, 31 Jan 2024 12:02:13 +0100
+Access-Control-Allow-Origin: *
+Connection: keep-alive
+Content-Length: 26
+Content-Type: application/json
+Vary: ORIGIN
+
+{"greeting":"Cheers Joe!"}
+```
+Note that the tests in the example `MainTest` class follow these same steps.
+
+## Using overrides
+
+The `Main#corsSupportForGreeting` method loads override settings for any other CORS set-up if the config contains a
+"cors" section. (That section is initially commented out in the example `application.yaml` file.) Not all applications
+need this feature, but the example shows how easy it is to add.
+
+With the same server running, repeat the `OPTIONS` request from above, but change the `Origin` header to refer to
+`other.com`:
+```shell
+curl -i -X OPTIONS \
+ -H "Access-Control-Request-Method: PUT" \
+ -H "Origin: http://other.com" \
+ -H "Host: here.com" \
+ http://localhost:8080/greet/greeting
+```
+```text
+HTTP/1.1 403 CORS origin is not in allowed list
+Date: Wed, 31 Jan 2024 12:02:51 +0100
+Connection: keep-alive
+Content-Length: 0
+```
+This fails because the app set up CORS using the "restrictive-cors" configuration in `application.yaml` which allows
+sharing only with `foo.com` and `there.com`, not with `other.com`.
+
+Stop the running app, uncomment the commented section at the end of `application.yaml`, and build and run the app again.
+```shell
+mvn package
+java -jar target/helidon-examples-cors.jar
+```
+Send the previous `OPTIONS` request again and note the successful result:
+```text
+HTTP/1.1 200 OK
+Date: Wed, 31 Jan 2024 12:05:36 +0100
+Access-Control-Allow-Methods: PUT
+Access-Control-Allow-Origin: http://other.com
+Access-Control-Max-Age: 3600
+Connection: keep-alive
+Content-Length: 0
+```
+The application uses the now-uncommented portion of the config file to override the rest of the CORS set-up. You can
+choose whatever key name you want for the override. Just make sure you tell your end users whatever the key is your app
+uses for overrides.
+
+A real application might read the normal configuration (`restrictive-cors`) from one config source and any overrides
+from another. This example combines them in one config source just for simplicity.
diff --git a/examples/cors/pom.xml b/examples/cors/pom.xml
new file mode 100644
index 000000000..70fbd8350
--- /dev/null
+++ b/examples/cors/pom.xml
@@ -0,0 +1,115 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples
+ helidon-examples-cors
+ 1.0.0-SNAPSHOT
+ Helidon Examples CORS SE
+
+
+ Basic illustration of CORS support in Helidon SE
+
+
+
+ io.helidon.examples.cors.Main
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-health
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-metrics
+ runtime
+
+
+ io.helidon.metrics
+ helidon-metrics-system-meters
+ runtime
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.http.media
+ helidon-http-media-jsonp
+
+
+ io.helidon.health
+ helidon-health-checks
+
+
+ io.helidon.logging
+ helidon-logging-jul
+ runtime
+
+
+ jakarta.json
+ jakarta.json-api
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
diff --git a/examples/cors/src/main/java/io/helidon/examples/cors/GreetService.java b/examples/cors/src/main/java/io/helidon/examples/cors/GreetService.java
new file mode 100644
index 000000000..bc4c37865
--- /dev/null
+++ b/examples/cors/src/main/java/io/helidon/examples/cors/GreetService.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.cors;
+
+import java.util.Collections;
+
+import io.helidon.config.Config;
+import io.helidon.http.Status;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+import jakarta.json.Json;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+
+/**
+ * A simple service to greet you. Examples:
+ *
+ * Get default greeting message:
+ * curl -X GET http://localhost:8080/greet
+ *
+ * Get greeting message for Joe:
+ * curl -X GET http://localhost:8080/greet/Joe
+ *
+ * Change greeting
+ * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting
+ *
+ * The message is returned as a JSON object
+ */
+
+public class GreetService implements HttpService {
+
+ /**
+ * The config value for the key {@code greeting}.
+ */
+ private String greeting;
+
+ private static final JsonBuilderFactory JSON_BF = Json.createBuilderFactory(Collections.emptyMap());
+
+ GreetService() {
+ Config config = Config.global();
+ this.greeting = config.get("app.greeting").asString().orElse("Ciao");
+ }
+
+ /**
+ * A service registers itself by updating the routine rules.
+ *
+ * @param rules the routing rules.
+ */
+ @Override
+ public void routing(HttpRules rules) {
+ rules .get("/", this::getDefaultMessageHandler)
+ .get("/{name}", this::getMessageHandler)
+ .put("/greeting", this::updateGreetingHandler);
+ }
+
+ /**
+ * Return a worldly greeting message.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) {
+ sendResponse(response, "World");
+ }
+
+ /**
+ * Return a greeting message using the name that was provided.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void getMessageHandler(ServerRequest request,
+ ServerResponse response) {
+ String name = request.path().pathParameters().get("name");
+ sendResponse(response, name);
+ }
+
+ private void sendResponse(ServerResponse response, String name) {
+ GreetingMessage msg = new GreetingMessage(String.format("%s %s!", greeting, name));
+ response.send(msg.forRest());
+ }
+
+ private void updateGreetingFromJson(JsonObject jo, ServerResponse response) {
+
+ if (!jo.containsKey(GreetingMessage.JSON_LABEL)) {
+ JsonObject jsonErrorObject = JSON_BF.createObjectBuilder()
+ .add("error", "No greeting provided")
+ .build();
+ response.status(Status.BAD_REQUEST_400)
+ .send(jsonErrorObject);
+ return;
+ }
+
+ greeting = GreetingMessage.fromRest(jo).getMessage();
+ response.status(Status.NO_CONTENT_204).send();
+ }
+
+ /**
+ * Set the greeting to use in future messages.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void updateGreetingHandler(ServerRequest request,
+ ServerResponse response) {
+ JsonObject jo = request.content().as(JsonObject.class);
+ updateGreetingFromJson(jo, response);
+ }
+}
diff --git a/examples/cors/src/main/java/io/helidon/examples/cors/GreetingMessage.java b/examples/cors/src/main/java/io/helidon/examples/cors/GreetingMessage.java
new file mode 100644
index 000000000..42dd76d6e
--- /dev/null
+++ b/examples/cors/src/main/java/io/helidon/examples/cors/GreetingMessage.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.cors;
+
+import java.util.Collections;
+
+import jakarta.json.Json;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonObjectBuilder;
+
+/**
+ * POJO for the greeting message exchanged between the server and the client.
+ */
+public class GreetingMessage {
+
+ /**
+ * Label for tagging a {@code GreetingMessage} instance in JSON.
+ */
+ public static final String JSON_LABEL = "greeting";
+
+ private static final JsonBuilderFactory JSON_BF = Json.createBuilderFactory(Collections.emptyMap());
+
+ private String message;
+
+ /**
+ * Create a new greeting with the specified message content.
+ *
+ * @param message the message to store in the greeting
+ */
+ public GreetingMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * Returns the message value.
+ *
+ * @return the message
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Sets the message value.
+ *
+ * @param message value to be set
+ */
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * Converts a JSON object (typically read from the request payload)
+ * into a {@code GreetingMessage}.
+ *
+ * @param jsonObject the {@link JsonObject} to convert.
+ * @return {@code GreetingMessage} set according to the provided object
+ */
+ public static GreetingMessage fromRest(JsonObject jsonObject) {
+ return new GreetingMessage(jsonObject.getString(JSON_LABEL));
+ }
+
+ /**
+ * Prepares a {@link JsonObject} corresponding to this instance.
+ *
+ * @return {@code JsonObject} representing this {@code GreetingMessage} instance
+ */
+ public JsonObject forRest() {
+ JsonObjectBuilder builder = JSON_BF.createObjectBuilder();
+ return builder.add(JSON_LABEL, message)
+ .build();
+ }
+}
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
new file mode 100644
index 000000000..b9167fbd8
--- /dev/null
+++ b/examples/cors/src/main/java/io/helidon/examples/cors/Main.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.cors;
+
+import java.util.logging.Logger;
+
+import io.helidon.config.Config;
+import io.helidon.cors.CrossOriginConfig;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.WebServerConfig;
+import io.helidon.webserver.cors.CorsSupport;
+import io.helidon.webserver.http.HttpRouting;
+
+/**
+ * Simple Hello World rest application.
+ */
+public final class Main {
+
+ /**
+ * Cannot be instantiated.
+ */
+ private Main() {
+ }
+
+ /**
+ * Application main entry point.
+ *
+ * @param args command line arguments.
+ */
+ public static void main(final String[] args) {
+ // load logging configuration
+ LogConfig.configureRuntime();
+
+ // initialize global config from default configuration
+ Config config = Config.create();
+ Config.global(config);
+
+ // Get webserver config from the "server" section of application.yaml
+ WebServerConfig.Builder builder = WebServer.builder();
+ WebServer server = builder
+ .config(config.get("server"))
+ .routing(Main::routing)
+ .build()
+ .start();
+
+ System.out.println("WEB server is up! http://localhost:" + server.port() + "/greet");
+ }
+
+ /**
+ * Setup routing.
+ *
+ * @param routing routing builder
+ */
+ static void routing(HttpRouting.Builder routing) {
+
+ // Note: Add the CORS routing *before* registering the GreetService routing.
+ routing.register("/greet", corsSupportForGreeting(), new GreetService());
+ }
+
+ private static CorsSupport corsSupportForGreeting() {
+ Config config = Config.global();
+
+ // The default CorsSupport object (obtained using CorsSupport.create()) allows sharing for any HTTP method and with any
+ // origin. Using CorsSupport.create(Config) with a missing config node yields a default CorsSupport, which might not be
+ // what you want. This example warns if either expected config node is missing and then continues with the default.
+
+ Config restrictiveConfig = config.get("restrictive-cors");
+ if (!restrictiveConfig.exists()) {
+ Logger.getLogger(Main.class.getName())
+ .warning("Missing restrictive config; continuing with default CORS support");
+ }
+
+ CorsSupport.Builder corsBuilder = CorsSupport.builder();
+
+ // Use possible overrides first.
+ config.get("cors")
+ .ifExists(c -> {
+ Logger.getLogger(Main.class.getName()).info("Using the override configuration");
+ corsBuilder.mappedConfig(c);
+ });
+ corsBuilder
+ .config(restrictiveConfig) // restricted sharing for PUT, DELETE
+ .addCrossOrigin(CrossOriginConfig.create()) // open sharing for other methods
+ .build();
+
+ return corsBuilder.build();
+ }
+}
diff --git a/examples/cors/src/main/java/io/helidon/examples/cors/package-info.java b/examples/cors/src/main/java/io/helidon/examples/cors/package-info.java
new file mode 100644
index 000000000..46b0ef2cd
--- /dev/null
+++ b/examples/cors/src/main/java/io/helidon/examples/cors/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example application showing CORS support in Helidon SE
+ *
+ * Start with {@link io.helidon.examples.cors.Main} class.
+ *
+ * @see io.helidon.examples.cors.Main
+ */
+package io.helidon.examples.cors;
diff --git a/examples/cors/src/main/resources/META-INF/openapi.yml b/examples/cors/src/main/resources/META-INF/openapi.yml
new file mode 100644
index 000000000..8886b61bf
--- /dev/null
+++ b/examples/cors/src/main/resources/META-INF/openapi.yml
@@ -0,0 +1,79 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+---
+openapi: 3.0.0
+info:
+ title: Helidon SE Quickstart Example
+ description: A very simple application to reply with friendly greetings
+ version: 1.0.0
+
+servers:
+ - url: http://localhost:8000
+ description: Local test server
+
+paths:
+ /greet:
+ get:
+ summary: Returns a generic greeting
+ description: Greets the user generically
+ responses:
+ default:
+ description: Simple JSON containing the greeting
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GreetingMessage'
+ /greet/greeting:
+ put:
+ summary: Set the greeting prefix
+ description: Permits the client to set the prefix part of the greeting ("Hello")
+ requestBody:
+ description: Conveys the new greeting prefix to use in building greetings
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GreetingMessage'
+ examples:
+ greeting:
+ summary: Example greeting message to update
+ value: New greeting message
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json: {}
+ /greet/{name}:
+ get:
+ summary: Returns a personalized greeting
+ parameters:
+ - name: name
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ default:
+ description: Simple JSON containing the greeting
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GreetingMessage'
+components:
+ schemas:
+ GreetingMessage:
+ properties:
+ message:
+ type: string
diff --git a/examples/cors/src/main/resources/application.yaml b/examples/cors/src/main/resources/application.yaml
new file mode 100644
index 000000000..adef82fb9
--- /dev/null
+++ b/examples/cors/src/main/resources/application.yaml
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+app:
+ greeting: "Hello"
+
+server:
+ port: 8080
+ host: 0.0.0.0
+
+restrictive-cors:
+ allow-origins: ["http://foo.com", "http://there.com"]
+ allow-methods: ["PUT", "DELETE"]
+
+# The example app uses the following for overriding other settings.
+#cors:
+# paths:
+# - path-pattern: /greeting
+# allow-origins: ["http://foo.com", "http://there.com", "http://other.com"]
+# allow-methods: ["PUT", "DELETE"]
+
diff --git a/examples/cors/src/main/resources/logging.properties b/examples/cors/src/main/resources/logging.properties
new file mode 100644
index 000000000..261494867
--- /dev/null
+++ b/examples/cors/src/main/resources/logging.properties
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+# Uncomment the following to see CORS-related decision-making
+# io.helidon.webserver.cors.level=FINE
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
new file mode 100644
index 000000000..5f293f303
--- /dev/null
+++ b/examples/cors/src/test/java/io/helidon/examples/cors/MainTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.cors;
+
+import java.util.List;
+import java.util.Optional;
+
+import io.helidon.common.media.type.MediaTypes;
+import io.helidon.config.Config;
+import io.helidon.cors.CrossOriginConfig;
+import io.helidon.http.Headers;
+import io.helidon.http.WritableHeaders;
+import io.helidon.webclient.http1.Http1Client;
+import io.helidon.webclient.http1.Http1ClientRequest;
+import io.helidon.webclient.http1.Http1ClientResponse;
+import io.helidon.webserver.WebServerConfig;
+import io.helidon.webserver.testing.junit5.ServerTest;
+import io.helidon.webserver.testing.junit5.SetUpServer;
+
+import jakarta.json.JsonObject;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import static io.helidon.http.HeaderNames.ACCESS_CONTROL_ALLOW_METHODS;
+import static io.helidon.http.HeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN;
+import static io.helidon.http.HeaderNames.ACCESS_CONTROL_REQUEST_METHOD;
+import static io.helidon.http.HeaderNames.HOST;
+import static io.helidon.http.HeaderNames.ORIGIN;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsEmptyCollection.empty;
+
+@SuppressWarnings("HttpUrlsUsage")
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+@ServerTest
+public class MainTest {
+
+ private final Http1Client client;
+
+ MainTest(Http1Client client) {
+ this.client = client;
+ }
+
+ @SetUpServer
+ public static void setup(WebServerConfig.Builder server) {
+ server.routing(Main::routing);
+ }
+
+ @Order(1) // Make sure this runs before the greeting message changes so responses are deterministic.
+ @Test
+ public void testHelloWorld() {
+ try (Http1ClientResponse response = client.get("/greet")
+ .accept(MediaTypes.APPLICATION_JSON)
+ .request()) {
+
+ assertThat(response.status().code(), is(200));
+
+ String payload = GreetingMessage.fromRest(response.entity().as(JsonObject.class)).getMessage();
+ assertThat(payload, is("Hello World!"));
+ }
+
+ try (Http1ClientResponse response = client.get("/greet/Joe")
+ .accept(MediaTypes.APPLICATION_JSON)
+ .request()) {
+
+ assertThat(response.status().code(), is(200));
+
+ String payload = GreetingMessage.fromRest(response.entity().as(JsonObject.class)).getMessage();
+ assertThat(payload, is("Hello Joe!"));
+ }
+
+ try (Http1ClientResponse response = client.put("/greet/greeting")
+ .accept(MediaTypes.APPLICATION_JSON)
+ .submit(new GreetingMessage("Hola").forRest())) {
+
+ assertThat(response.status().code(), is(204));
+ }
+
+ try (Http1ClientResponse response = client.get("/greet/Jose")
+ .accept(MediaTypes.APPLICATION_JSON)
+ .request()) {
+
+ assertThat(response.status().code(), is(200));
+
+ String payload = GreetingMessage.fromRest(response.entity().as(JsonObject.class)).getMessage();
+ assertThat(payload, is("Hola Jose!"));
+ }
+
+ try (Http1ClientResponse response = client.get("/observe/health").request()) {
+ assertThat(response.status().code(), is(204));
+ }
+
+ try (Http1ClientResponse response = client.get("/observe/metrics").request()) {
+ assertThat(response.status().code(), is(200));
+ }
+ }
+
+ @Order(10)
+ // Run after the non-CORS tests (so the greeting is Hola) but before the CORS test that changes the greeting again.
+ @Test
+ void testAnonymousGreetWithCors() {
+ try (Http1ClientResponse response = client.get()
+ .path("/greet")
+ .accept(MediaTypes.APPLICATION_JSON)
+ .headers(it -> it
+ .set(ORIGIN, "http://foo.com")
+ .set(HOST, "here.com"))
+ .request()) {
+
+ assertThat(response.status().code(), is(200));
+ String payload = GreetingMessage.fromRest(response.entity().as(JsonObject.class)).getMessage();
+ assertThat(payload, containsString("Hola World"));
+ Headers responseHeaders = response.headers();
+ Optional allowOrigin = responseHeaders.value(ACCESS_CONTROL_ALLOW_ORIGIN);
+ assertThat("Expected CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN + " is absent",
+ allowOrigin.isPresent(), is(true));
+ assertThat(allowOrigin.get(), is("*"));
+ }
+ }
+
+ @Order(11) // Run after the non-CORS tests but before other CORS tests.
+ @Test
+ void testGreetingChangeWithCors() {
+ // Send the pre-flight request and check the response.
+ WritableHeaders> preFlightHeaders = WritableHeaders.create();
+ try (Http1ClientResponse response = client.options()
+ .path("/greet/greeting")
+ .headers(it -> it
+ .set(ORIGIN, "http://foo.com")
+ .set(HOST, "here.com")
+ .set(ACCESS_CONTROL_REQUEST_METHOD, "PUT"))
+ .request()) {
+ response.headers().forEach(preFlightHeaders::add);
+ List allowMethods = preFlightHeaders.values(ACCESS_CONTROL_ALLOW_METHODS);
+ assertThat("pre-flight response does not include " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS,
+ allowMethods, not(empty()));
+ assertThat(allowMethods, hasItem("PUT"));
+ List allowOrigins = preFlightHeaders.values(ACCESS_CONTROL_ALLOW_ORIGIN);
+ assertThat("pre-flight response does not include " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN,
+ allowOrigins, not(empty()));
+ assertThat("Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN
+ + " should contain '*' but does not; " + allowOrigins,
+ allowOrigins, hasItem("http://foo.com"));
+ }
+
+ // Send the follow-up request.
+ GreetingMessage payload = new GreetingMessage("Cheers");
+ try (Http1ClientResponse response = client.put("/greet/greeting")
+ .accept(MediaTypes.APPLICATION_JSON)
+ .headers(headers -> {
+ headers.set(ORIGIN, "http://foo.com");
+ headers.set(HOST, "here.com");
+ preFlightHeaders.forEach(headers::add);
+ }).submit(payload.forRest())) {
+
+ assertThat(response.status().code(), is(204));
+ List allowOrigins = preFlightHeaders.values(ACCESS_CONTROL_ALLOW_ORIGIN);
+ assertThat("Expected CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN + " has no value(s)",
+ allowOrigins, not(empty()));
+ assertThat("Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN
+ + " should contain '*' but does not; " + allowOrigins,
+ allowOrigins, hasItem("http://foo.com"));
+ }
+ }
+
+ @Order(12) // Run after CORS test changes greeting to Cheers.
+ @Test
+ void testNamedGreetWithCors() {
+ try (Http1ClientResponse response = client.get()
+ .path("/greet/Maria")
+ .headers(headers -> headers
+ .set(ORIGIN, "http://foo.com")
+ .set(HOST, "here.com"))
+ .request()) {
+ assertThat("HTTP response", response.status().code(), is(200));
+ String payload = GreetingMessage.fromRest(response.entity().as(JsonObject.class)).getMessage();
+ assertThat(payload, containsString("Cheers Maria"));
+ Headers responseHeaders = response.headers();
+ Optional allowOrigin = responseHeaders.value(ACCESS_CONTROL_ALLOW_ORIGIN);
+ assertThat("Expected CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN + " is absent",
+ allowOrigin.isPresent(), is(true));
+ assertThat(allowOrigin.get(), is("*"));
+ }
+ }
+
+ @Order(100) // After all other tests, so we can rely on deterministic greetings.
+ @Test
+ void testGreetingChangeWithCorsAndOtherOrigin() {
+ Http1ClientRequest request = client.put()
+ .path("/greet/greeting");
+ request.headers(headers -> {
+ headers.set(ORIGIN, "http://other.com");
+ headers.set(HOST, "here.com");
+ });
+
+ GreetingMessage payload = new GreetingMessage("Ahoy");
+ try (Http1ClientResponse response = request.submit(payload.forRest())) {
+ // Result depends on whether we are using overrides or not.
+ boolean isOverriding = Config.create().get("cors").exists();
+ assertThat("HTTP response3", response.status().code(), is(isOverriding ? 204 : 403));
+ }
+ }
+}
diff --git a/examples/dbclient/README.md b/examples/dbclient/README.md
new file mode 100644
index 000000000..5345384f7
--- /dev/null
+++ b/examples/dbclient/README.md
@@ -0,0 +1,14 @@
+# Helidon DB Client Examples
+
+Each subdirectory contains example code that highlights specific aspects of
+Helidon DB Client.
+
+build examples in all folders (including the common folder) by
+```shell
+mvn package
+```
+in the current folder.
+
+---
+
+Pokémon, and Pokémon character names are trademarks of Nintendo.
diff --git a/examples/dbclient/common/README.md b/examples/dbclient/common/README.md
new file mode 100644
index 000000000..875f95c92
--- /dev/null
+++ b/examples/dbclient/common/README.md
@@ -0,0 +1,22 @@
+# Common library for the DB Client examples
+
+shall be built using
+
+```shell
+
+mvn install
+
+```
+
+to be accessible for the DB Client examples
+
+or make the whole pack of DB Client examples at the level above by
+
+```shell
+
+cd ../
+mvn package
+
+```
+
+and get all DB Client examples compiled at once.
\ No newline at end of file
diff --git a/examples/dbclient/common/pom.xml b/examples/dbclient/common/pom.xml
new file mode 100644
index 000000000..51a19915c
--- /dev/null
+++ b/examples/dbclient/common/pom.xml
@@ -0,0 +1,54 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples.dbclient
+ helidon-examples-dbclient-project
+ 1.0.0-SNAPSHOT
+
+
+ helidon-examples-dbclient-common
+ 1.0.0-SNAPSHOT
+ Helidon Examples DB Client Common
+
+
+
+ io.helidon.dbclient
+ helidon-dbclient
+
+
+ io.helidon.http.media
+ helidon-http-media-jsonp
+
+
+ io.helidon.http.media
+ helidon-http-media-jsonb
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ jakarta.json
+ jakarta.json-api
+
+
+
diff --git a/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/AbstractPokemonService.java b/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/AbstractPokemonService.java
new file mode 100644
index 000000000..ce10b3e90
--- /dev/null
+++ b/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/AbstractPokemonService.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.dbclient.common;
+
+import io.helidon.common.parameters.Parameters;
+import io.helidon.dbclient.DbClient;
+import io.helidon.dbclient.DbTransaction;
+import io.helidon.http.NotFoundException;
+import io.helidon.webserver.http.Handler;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+import jakarta.json.JsonObject;
+
+/**
+ * Common methods that do not differ between JDBC and MongoDB.
+ */
+public abstract class AbstractPokemonService implements HttpService {
+
+ private final DbClient dbClient;
+
+ /**
+ * Create a new Pokémon service with a DB client.
+ *
+ * @param dbClient DB client to use for database operations
+ */
+ protected AbstractPokemonService(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+
+ @Override
+ public void routing(HttpRules rules) {
+ rules
+ .get("/", this::listPokemons)
+ // create new
+ .put("/", Handler.create(Pokemon.class, this::insertPokemon))
+ // update existing
+ .post("/{name}/type/{type}", this::insertPokemonSimple)
+ // delete all
+ .delete("/", this::deleteAllPokemons)
+ // get one
+ .get("/{name}", this::getPokemon)
+ // delete one
+ .delete("/{name}", this::deletePokemon)
+ // example of transactional API (local transaction only!)
+ .put("/transactional", this::transactional)
+ // update one (TODO this is intentionally wrong - should use JSON request, just to make it simple we use path)
+ .put("/{name}/type/{type}", this::updatePokemonType);
+ }
+
+ /**
+ * The DB client associated with this service.
+ *
+ * @return DB client instance
+ */
+ protected DbClient dbClient() {
+ return dbClient;
+ }
+
+ /**
+ * This method is left unimplemented to show differences between native statements that can be used.
+ *
+ * @param req Server request
+ * @param res Server response
+ */
+ protected abstract void deleteAllPokemons(ServerRequest req, ServerResponse res);
+
+ /**
+ * Insert new Pokémon with specified name.
+ *
+ * @param pokemon pokemon request entity
+ * @param res the server response
+ */
+ private void insertPokemon(Pokemon pokemon, ServerResponse res) {
+ long count = dbClient.execute().createNamedInsert("insert2")
+ .namedParam(pokemon)
+ .execute();
+ res.send("Inserted: " + count + " values");
+ }
+
+ /**
+ * Insert new Pokémon with specified name.
+ *
+ * @param req the server request
+ * @param res the server response
+ */
+ private void insertPokemonSimple(ServerRequest req, ServerResponse res) {
+ Parameters params = req.path().pathParameters();
+ // Test Pokémon POJO mapper
+ Pokemon pokemon = new Pokemon(params.get("name"), params.get("type"));
+
+ long count = dbClient.execute().createNamedInsert("insert2")
+ .namedParam(pokemon)
+ .execute();
+ res.send("Inserted: " + count + " values");
+ }
+
+ /**
+ * Get a single Pokémon by name.
+ *
+ * @param req server request
+ * @param res server response
+ */
+ private void getPokemon(ServerRequest req, ServerResponse res) {
+ String pokemonName = req.path().pathParameters().get("name");
+ res.send(dbClient.execute()
+ .namedGet("select-one", pokemonName)
+ .orElseThrow(() -> new NotFoundException("Pokemon " + pokemonName + " not found"))
+ .as(JsonObject.class));
+ }
+
+ /**
+ * Return JsonArray with all stored Pokémon.
+ *
+ * @param req the server request
+ * @param res the server response
+ */
+ private void listPokemons(ServerRequest req, ServerResponse res) {
+ res.send(dbClient.execute()
+ .namedQuery("select-all")
+ .map(it -> it.as(JsonObject.class))
+ .toList());
+ }
+
+ /**
+ * Update a Pokémon.
+ * Uses a transaction.
+ *
+ * @param req the server request
+ * @param res the server response
+ */
+ private void updatePokemonType(ServerRequest req, ServerResponse res) {
+ Parameters params = req.path().pathParameters();
+ String name = params.get("name");
+ String type = params.get("type");
+ long count = dbClient.execute()
+ .createNamedUpdate("update")
+ .addParam("name", name)
+ .addParam("type", type)
+ .execute();
+ res.send("Updated: " + count + " values");
+ }
+
+ private void transactional(ServerRequest req, ServerResponse res) {
+ Pokemon pokemon = req.content().as(Pokemon.class);
+ DbTransaction tx = dbClient.transaction();
+ try {
+ long count = tx.createNamedGet("select-for-update")
+ .namedParam(pokemon)
+ .execute()
+ .map(dbRow -> tx.createNamedUpdate("update")
+ .namedParam(pokemon)
+ .execute())
+ .orElse(0L);
+ tx.commit();
+ res.send("Updated " + count + " records");
+ } catch (Throwable t) {
+ tx.rollback();
+ throw t;
+ }
+ }
+
+ /**
+ * Delete a Pokémon with specified name (key).
+ *
+ * @param req the server request
+ * @param res the server response
+ */
+ private void deletePokemon(ServerRequest req, ServerResponse res) {
+ String name = req.path().pathParameters().get("name");
+ long count = dbClient.execute().namedDelete("delete", name);
+ res.send("Deleted: " + count + " values");
+ }
+}
diff --git a/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/Pokemon.java b/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/Pokemon.java
new file mode 100644
index 000000000..fe2078826
--- /dev/null
+++ b/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/Pokemon.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.dbclient.common;
+
+import io.helidon.common.Reflected;
+
+/**
+ * POJO representing a very simplified Pokémon.
+ */
+@Reflected
+public class Pokemon {
+ private String name;
+ private String type;
+
+ /**
+ * Default constructor.
+ */
+ public Pokemon() {
+ // JSON-B
+ }
+
+ /**
+ * Create Pokémon with name and type.
+ *
+ * @param name name of the beast
+ * @param type type of the beast
+ */
+ public Pokemon(String name, String type) {
+ this.name = name;
+ this.type = type;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+}
diff --git a/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/PokemonMapper.java b/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/PokemonMapper.java
new file mode 100644
index 000000000..a0a0e560d
--- /dev/null
+++ b/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/PokemonMapper.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.dbclient.common;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import io.helidon.dbclient.DbColumn;
+import io.helidon.dbclient.DbMapper;
+import io.helidon.dbclient.DbRow;
+
+/**
+ * Maps database statements to {@link io.helidon.examples.dbclient.common.Pokemon} class.
+ */
+public class PokemonMapper implements DbMapper {
+
+ @Override
+ public Pokemon read(DbRow row) {
+ DbColumn name = row.column("name");
+ // we know that in mongo this is not true
+ if (null == name) {
+ name = row.column("_id");
+ }
+
+ DbColumn type = row.column("type");
+ return new Pokemon(name.get(String.class), type.get(String.class));
+ }
+
+ @Override
+ public Map toNamedParameters(Pokemon value) {
+ Map map = new HashMap<>(1);
+ map.put("name", value.getName());
+ map.put("type", value.getType());
+ return map;
+ }
+
+ @Override
+ public List toIndexedParameters(Pokemon value) {
+ List list = new ArrayList<>(2);
+ list.add(value.getName());
+ list.add(value.getType());
+ return list;
+ }
+}
diff --git a/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/PokemonMapperProvider.java b/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/PokemonMapperProvider.java
new file mode 100644
index 000000000..5f8784bb8
--- /dev/null
+++ b/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/PokemonMapperProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.dbclient.common;
+
+import java.util.Optional;
+
+import io.helidon.common.Weight;
+import io.helidon.dbclient.DbMapper;
+import io.helidon.dbclient.spi.DbMapperProvider;
+
+/**
+ * Provides pokemon mappers.
+ */
+@Weight(100)
+public class PokemonMapperProvider implements DbMapperProvider {
+ private static final PokemonMapper MAPPER = new PokemonMapper();
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Optional> mapper(Class type) {
+ if (type.equals(Pokemon.class)) {
+ return Optional.of((DbMapper) MAPPER);
+ }
+ return Optional.empty();
+ }
+}
diff --git a/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/package-info.java b/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/package-info.java
new file mode 100644
index 000000000..78d7f6680
--- /dev/null
+++ b/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Common classes shared by JDBC and MongoDB examples.
+ */
+package io.helidon.examples.dbclient.common;
diff --git a/examples/dbclient/common/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbMapperProvider b/examples/dbclient/common/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbMapperProvider
new file mode 100644
index 000000000..886f95d56
--- /dev/null
+++ b/examples/dbclient/common/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbMapperProvider
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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.examples.dbclient.common.PokemonMapperProvider
diff --git a/examples/dbclient/jdbc/README.md b/examples/dbclient/jdbc/README.md
new file mode 100644
index 000000000..ddc40f52d
--- /dev/null
+++ b/examples/dbclient/jdbc/README.md
@@ -0,0 +1,69 @@
+# Helidon DB Client JDBC Example
+
+This example shows how to run Helidon DB Client over JDBC.
+
+Examples are given for H2, Oracle, or MySQL databases (note that MySQL is currently not supported for GraalVM native image)
+
+Uncomment the appropriate dependencies in the pom.xml for the desired database (H2, Oracle, or MySQL) and insure others are commented.
+
+Uncomment the appropriate configuration in the application.xml for the desired database (H2, Oracle, or MySQL) and insure others are commented.
+
+## Build
+
+```shell
+mvn package
+```
+
+This example may also be run as a GraalVM native image in which case can be built using the following:
+
+```shell
+mvn package -Pnative-image
+```
+
+
+## Run
+
+This example requires a database.
+
+Instructions for H2 can be found here: http://www.h2database.com/html/cheatSheet.html
+
+Instructions for Oracle can be found here: https://github.com/oracle/docker-images/tree/master/OracleDatabase/SingleInstance
+
+MySQL can be run as a docker container with the following command:
+```shell
+docker run --rm --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=pokemon -e MYSQL_USER=user -e MYSQL_PASSWORD=changeit mysql:5.7
+```
+
+
+Then run the application:
+
+```shell
+java -jar target/helidon-examples-dbclient-jdbc.jar
+```
+or in the case of native image
+```shell
+./target/helidon-examples-dbclient-jdbc
+```
+
+## Exercise
+
+The application has the following endpoints:
+
+- http://localhost:8079/db - the main business endpoint (see `curl` commands below)
+- http://localhost:8079/metrics - the metrics endpoint (query adds application metrics)
+- http://localhost:8079/health - has a custom database health check
+
+Application also connects to zipkin on default address.
+The query operation adds database trace.
+
+`curl` commands:
+
+- `curl http://localhost:8079/db` - list all Pokemon in the database
+- `curl -i -X PUT -d '{"name":"Squirtle","type":"water"}' http://localhost:8079/db` - add a new pokemon
+- `curl http://localhost:8079/db/Squirtle` - get a single pokemon
+
+The application also supports update and delete - see `PokemonService.java` for bound endpoints.
+
+---
+
+Pokémon, and Pokémon character names are trademarks of Nintendo.
diff --git a/examples/dbclient/jdbc/pom.xml b/examples/dbclient/jdbc/pom.xml
new file mode 100644
index 000000000..d59a3119b
--- /dev/null
+++ b/examples/dbclient/jdbc/pom.xml
@@ -0,0 +1,211 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+
+ helidon-examples-dbclient-jdbc
+ 1.0.0-SNAPSHOT
+ Helidon Examples DB Client JDBC
+
+
+ io.helidon.examples.dbclient.jdbc.JdbcExampleMain
+
+
+
+
+ io.helidon.logging
+ helidon-logging-jul
+ runtime
+
+
+ io.helidon.tracing
+ helidon-tracing
+
+
+ io.helidon.tracing.providers
+ helidon-tracing-providers-zipkin
+
+
+ io.helidon.dbclient
+ helidon-dbclient-jdbc
+
+
+ io.helidon.dbclient
+ helidon-dbclient-tracing
+
+
+ io.helidon.dbclient
+ helidon-dbclient-metrics
+
+
+ io.helidon.dbclient
+ helidon-dbclient-metrics-hikari
+
+
+ io.helidon.dbclient
+ helidon-dbclient-health
+
+
+ io.helidon.dbclient
+ helidon-dbclient-jsonp
+
+
+
+
+
+ io.helidon.integrations.db
+ ojdbc
+
+
+
+ org.slf4j
+ slf4j-jdk14
+
+
+ io.helidon.http.media
+ helidon-http-media-jsonb
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-tracing
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.examples.dbclient
+ helidon-examples-dbclient-common
+ ${project.version}
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-health
+
+
+ io.helidon.health
+ helidon-health-checks
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-metrics
+ runtime
+
+
+ io.helidon.metrics
+ helidon-metrics-system-meters
+ runtime
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+ org.testcontainers
+ mysql
+ test
+
+
+ org.testcontainers
+ oracle-xe
+
+
+ com.mysql
+ mysql-connector-j
+ test
+
+
+ io.helidon.integrations.db
+ h2
+ test
+
+
+ io.helidon.webclient
+ helidon-webclient
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+
+ false
+
+
+
+
+
+
+
+
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
new file mode 100644
index 000000000..1f9caabb0
--- /dev/null
+++ b/examples/dbclient/jdbc/src/main/java/io/helidon/examples/dbclient/jdbc/JdbcExampleMain.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.dbclient.jdbc;
+
+import io.helidon.config.Config;
+import io.helidon.dbclient.DbClient;
+import io.helidon.dbclient.health.DbClientHealthCheck;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.WebServerConfig;
+import io.helidon.webserver.observe.ObserveFeature;
+import io.helidon.webserver.observe.health.HealthObserver;
+
+/**
+ * Simple Hello World rest application.
+ */
+public final class JdbcExampleMain {
+
+ /**
+ * Cannot be instantiated.
+ */
+ private JdbcExampleMain() {
+ }
+
+ /**
+ * Application main entry point.
+ *
+ * @param args command line arguments.
+ */
+ public static void main(String[] args) {
+ // load logging configuration
+ LogConfig.configureRuntime();
+
+ // Prepare routing for the server
+ WebServer server = setupServer(WebServer.builder());
+
+ System.out.println("WEB server is up! http://localhost:" + server.port() + "/");
+ }
+
+ static WebServer setupServer(WebServerConfig.Builder builder) {
+ // By default, this will pick up application.yaml from the classpath
+ Config config = Config.global();
+
+ Config dbConfig = config.get("db");
+ DbClient dbClient = DbClient.create(dbConfig);
+
+ ObserveFeature observe = ObserveFeature.builder()
+ .config(config.get("server.features.observe"))
+ .addObserver(HealthObserver.builder()
+ .addCheck(DbClientHealthCheck.create(dbClient, dbConfig.get("health-check")))
+ .build())
+ .build();
+
+ return builder
+ .config(config.get("server"))
+ .addFeature(observe)
+ .routing(routing -> routing.register("/db", new PokemonService(dbClient)))
+ .build()
+ .start();
+ }
+}
diff --git a/examples/dbclient/jdbc/src/main/java/io/helidon/examples/dbclient/jdbc/PokemonService.java b/examples/dbclient/jdbc/src/main/java/io/helidon/examples/dbclient/jdbc/PokemonService.java
new file mode 100644
index 000000000..fc75bf6d4
--- /dev/null
+++ b/examples/dbclient/jdbc/src/main/java/io/helidon/examples/dbclient/jdbc/PokemonService.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.dbclient.jdbc;
+
+import io.helidon.dbclient.DbClient;
+import io.helidon.examples.dbclient.common.AbstractPokemonService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+/**
+ * Example service using a database.
+ */
+public class PokemonService extends AbstractPokemonService {
+
+ PokemonService(DbClient dbClient) {
+ super(dbClient);
+
+ // dirty hack to prepare database for our POC
+ // MySQL init
+ long count = dbClient().execute().namedDml("create-table");
+ System.out.println(count);
+ }
+
+ @Override
+ protected void deleteAllPokemons(ServerRequest req, ServerResponse res) {
+ // this is to show how ad-hoc statements can be executed (and their naming in Tracing and Metrics)
+ long count = dbClient().execute().createDelete("DELETE FROM pokemons").execute();
+ res.send("Deleted: " + count + " values");
+ }
+}
diff --git a/examples/dbclient/jdbc/src/main/java/io/helidon/examples/dbclient/jdbc/package-info.java b/examples/dbclient/jdbc/src/main/java/io/helidon/examples/dbclient/jdbc/package-info.java
new file mode 100644
index 000000000..f54ce8e11
--- /dev/null
+++ b/examples/dbclient/jdbc/src/main/java/io/helidon/examples/dbclient/jdbc/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Quick start demo application.
+ */
+package io.helidon.examples.dbclient.jdbc;
diff --git a/examples/dbclient/jdbc/src/main/resources/application.yaml b/examples/dbclient/jdbc/src/main/resources/application.yaml
new file mode 100644
index 000000000..cb175d49d
--- /dev/null
+++ b/examples/dbclient/jdbc/src/main/resources/application.yaml
@@ -0,0 +1,99 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8079
+ host: 0.0.0.0
+
+tracing:
+ service: jdbc-db
+
+db:
+ source: jdbc
+ connection:
+ #
+ # H2 configuration
+ #
+ # Embedded mode (does not work with native image)
+# url: jdbc:h2:~/test
+ # Server mode, run: docker run --rm --name h2 -p 9092:9082 -p 8082:8082 nemerosa/h2
+# url: "jdbc:h2:tcp://localhost:9092/~test"
+# username: sa
+# password:
+# poolName: h2
+ #
+ # MySQL configuration
+ #
+ # docker run --rm --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root \
+ # -e MYSQL_DATABASE=pokemon -e MYSQL_USER=user -e MYSQL_PASSWORD=changeit mysql:5.7
+# url: jdbc:mysql://127.0.0.1:3306/pokemon?useSSL=false
+# username: user
+# password: changeit
+# poolName: mysql
+ #
+ # Oracle configuration
+ #
+ # docker run --rm --name xe -p 1521:1521 -p 8888:8080 -e ORACLE_PWD=oracle wnameless/oracle-xe-11g-r2
+ url: jdbc:oracle:thin:@localhost:1521/XE
+ username: system
+ password: oracle
+ poolName: oracle
+ initializationFailTimeout: -1
+ connectionTimeout: 2000
+ helidon:
+ pool-metrics:
+ 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."
+ health-check:
+ type: "query"
+ statementName: "health-check"
+ services:
+ tracing:
+ # would trace all statement names that start with select-
+ - statement-names: ["select-.*"]
+ # would trace all delete statements
+ - statement-types: ["DELETE"]
+ metrics:
+ - 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:
+ # Health check query statement for MySQL and H2 databases
+# health-check: "SELECT 0"
+ # Health check query statement for Oracle database
+ health-check: "SELECT 1 FROM DUAL"
+ # Insert new pokemon
+ create-table: "CREATE TABLE pokemons (name VARCHAR(64) NOT NULL PRIMARY KEY, type VARCHAR(32))"
+ insert1: "INSERT INTO pokemons VALUES(?, ?)"
+ insert2: "INSERT INTO pokemons VALUES(:name, :type)"
+ select-by-type: "SELECT * FROM pokemons WHERE type = ?"
+ select-one: "SELECT * FROM pokemons WHERE name = ?"
+ select-all: "SELECT * FROM pokemons"
+ select-for-update: "SELECT * FROM pokemons WHERE name = :name for UPDATE"
+ update: "UPDATE pokemons SET type = :type WHERE name = :name"
+ delete: "DELETE FROM pokemons WHERE name = ?"
diff --git a/examples/dbclient/jdbc/src/main/resources/logging.properties b/examples/dbclient/jdbc/src/main/resources/logging.properties
new file mode 100644
index 000000000..550b8d9a4
--- /dev/null
+++ b/examples/dbclient/jdbc/src/main/resources/logging.properties
@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# Global default logging level. Can be overridden 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
+io.helidon.logging.jul.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
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
diff --git a/examples/dbclient/jdbc/src/test/java/io/helidon/examples/dbclient/jdbc/AbstractPokemonServiceTest.java b/examples/dbclient/jdbc/src/test/java/io/helidon/examples/dbclient/jdbc/AbstractPokemonServiceTest.java
new file mode 100644
index 000000000..384e7daa1
--- /dev/null
+++ b/examples/dbclient/jdbc/src/test/java/io/helidon/examples/dbclient/jdbc/AbstractPokemonServiceTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2023, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.dbclient.jdbc;
+
+import java.util.List;
+import java.util.Map;
+
+import io.helidon.http.Status;
+import io.helidon.http.media.jsonp.JsonpSupport;
+import io.helidon.webclient.api.ClientResponseTyped;
+import io.helidon.webclient.api.WebClient;
+import io.helidon.webserver.WebServer;
+
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+abstract class AbstractPokemonServiceTest {
+ private static final JsonBuilderFactory JSON_FACTORY = Json.createBuilderFactory(Map.of());
+
+ private static WebServer server;
+ private static WebClient client;
+
+ static void beforeAll() {
+ server = JdbcExampleMain.setupServer(WebServer.builder());
+ client = WebClient.create(config -> config.baseUri("http://localhost:" + server.port())
+ .addMediaSupport(JsonpSupport.create()));
+ }
+
+ static void afterAll() {
+ if (server != null && server.isRunning()) {
+ server.stop();
+ }
+ }
+
+ @Test
+ void testListAndDeleteAllPokemons() {
+ List names = listAllPokemons();
+ assertThat(names.isEmpty(), is(true));
+
+ String endpoint = String.format("/db/%s/type/%s", "Raticate", 1);
+ ClientResponseTyped response = client.post(endpoint).request(String.class);
+ assertThat(response.status(), is(Status.OK_200));
+
+ names = listAllPokemons();
+ assertThat(names.size(), is(1));
+ assertThat(names.getFirst(), is("Raticate"));
+
+ response = client.delete("/db").request(String.class);
+ assertThat(response.status(), is(Status.OK_200));
+
+ names = listAllPokemons();
+ assertThat(names.isEmpty(), is(true));
+ }
+
+ @Test
+ void testAddUpdateDeletePokemon() {
+ ClientResponseTyped response;
+ ClientResponseTyped jsonResponse;
+ JsonObject pokemon = JSON_FACTORY.createObjectBuilder()
+ .add("type", 1)
+ .add("name", "Raticate")
+ .build();
+
+ // Add new pokemon
+ response = client.put("/db").submit(pokemon, String.class);
+ assertThat(response.entity(), is("Inserted: 1 values"));
+
+ // Get the new pokemon added
+ jsonResponse = client.get("/db/Raticate").request(JsonObject.class);
+ assertThat(jsonResponse.status(), is(Status.OK_200));
+ assertThat(getName(jsonResponse.entity()), is("Raticate"));
+ assertThat(getType(jsonResponse.entity()), is("1"));
+
+ // Update pokemon
+ response = client.put("/db/Raticate/type/2").request(String.class);
+ assertThat(response.status(), is(Status.OK_200));
+
+ // Verify updated pokemon
+ jsonResponse = client.get("/db/Raticate").request(JsonObject.class);
+ assertThat(jsonResponse.status(), is(Status.OK_200));
+ assertThat(getName(jsonResponse.entity()), is("Raticate"));
+ assertThat(getType(jsonResponse.entity()), is("2"));
+
+ // Delete Pokemon
+ response = client.delete("/db/Raticate").request(String.class);
+ assertThat(response.status(), is(Status.OK_200));
+
+ // Verify pokemon is correctly deleted
+ response = client.get("/db/Raticate").request(String.class);
+ assertThat(response.status(), is(Status.NOT_FOUND_404));
+ }
+
+ private List listAllPokemons() {
+ ClientResponseTyped response = client.get("/db").request(JsonArray.class);
+ assertThat(response.status(), is(Status.OK_200));
+ return response.entity().stream().map(e -> getName(e.asJsonObject())).toList();
+ }
+
+ private String getName(JsonObject json) {
+ return json.containsKey("name")
+ ? json.getString("name")
+ : json.getString("NAME");
+ }
+
+ private String getType(JsonObject json) {
+ return json.containsKey("type")
+ ? json.getString("type")
+ : json.getString("TYPE");
+ }
+}
diff --git a/examples/dbclient/jdbc/src/test/java/io/helidon/examples/dbclient/jdbc/PokemonServiceH2IT.java b/examples/dbclient/jdbc/src/test/java/io/helidon/examples/dbclient/jdbc/PokemonServiceH2IT.java
new file mode 100644
index 000000000..7f5c8dee1
--- /dev/null
+++ b/examples/dbclient/jdbc/src/test/java/io/helidon/examples/dbclient/jdbc/PokemonServiceH2IT.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2023, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.dbclient.jdbc;
+
+import java.util.Map;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import static io.helidon.config.ConfigSources.classpath;
+
+@Testcontainers(disabledWithoutDocker = true)
+class PokemonServiceH2IT extends AbstractPokemonServiceTest {
+ private static final DockerImageName H2_IMAGE = DockerImageName.parse("nemerosa/h2");
+
+ @Container
+ static GenericContainer> container = new GenericContainer<>(H2_IMAGE)
+ .withExposedPorts(9082)
+ .waitingFor(Wait.forLogMessage("(.*)Web Console server running at(.*)", 1));
+
+ @BeforeAll
+ static void start() {
+ String url = String.format("jdbc:h2:tcp://localhost:%s/~./test", container.getMappedPort(9082));
+ Config.global(Config.builder()
+ .addSource(ConfigSources.create(Map.of("db.connection.url", url)))
+ .addSource(classpath("application-h2-test.yaml"))
+ .build());
+ beforeAll();
+ }
+
+ @AfterAll
+ static void stop() {
+ afterAll();
+ }
+}
diff --git a/examples/dbclient/jdbc/src/test/java/io/helidon/examples/dbclient/jdbc/PokemonServiceMySQLIT.java b/examples/dbclient/jdbc/src/test/java/io/helidon/examples/dbclient/jdbc/PokemonServiceMySQLIT.java
new file mode 100644
index 000000000..eee51c64b
--- /dev/null
+++ b/examples/dbclient/jdbc/src/test/java/io/helidon/examples/dbclient/jdbc/PokemonServiceMySQLIT.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2023, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.dbclient.jdbc;
+
+import java.util.Map;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.testcontainers.containers.MySQLContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import static io.helidon.config.ConfigSources.classpath;
+
+@Testcontainers(disabledWithoutDocker = true)
+class PokemonServiceMySQLIT extends AbstractPokemonServiceTest {
+
+ @Container
+ static MySQLContainer> container = new MySQLContainer<>("mysql:8.0.36")
+ .withUsername("user")
+ .withPassword("changeit")
+ .withNetworkAliases("mysql")
+ .withDatabaseName("pokemon");
+
+ @BeforeAll
+ static void start() {
+ Config.global(Config.builder()
+ .addSource(ConfigSources.create(Map.of("db.connection.url", container.getJdbcUrl())))
+ .addSource(classpath("application-mysql-test.yaml"))
+ .build());
+ beforeAll();
+ }
+
+ @AfterAll
+ static void stop() {
+ afterAll();
+ }
+
+}
diff --git a/examples/dbclient/jdbc/src/test/java/io/helidon/examples/dbclient/jdbc/PokemonServiceOracleIT.java b/examples/dbclient/jdbc/src/test/java/io/helidon/examples/dbclient/jdbc/PokemonServiceOracleIT.java
new file mode 100644
index 000000000..a37525049
--- /dev/null
+++ b/examples/dbclient/jdbc/src/test/java/io/helidon/examples/dbclient/jdbc/PokemonServiceOracleIT.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.dbclient.jdbc;
+
+import java.util.Map;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.testcontainers.containers.OracleContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import static io.helidon.config.ConfigSources.classpath;
+
+@Testcontainers(disabledWithoutDocker = true)
+public class PokemonServiceOracleIT extends AbstractPokemonServiceTest {
+
+ private static final DockerImageName image = DockerImageName.parse("wnameless/oracle-xe-11g-r2")
+ .asCompatibleSubstituteFor("gvenzl/oracle-xe");
+
+ @Container
+ static OracleContainer container = new OracleContainer(image)
+ .withExposedPorts(1521, 8080)
+ .withDatabaseName("XE")
+ .usingSid()
+ .waitingFor(Wait.forListeningPorts(1521, 8080));
+
+ @BeforeAll
+ static void start() {
+ Config.global(Config.builder()
+ .addSource(ConfigSources.create(Map.of("db.connection.url", container.getJdbcUrl())))
+ .addSource(classpath("application-oracle-test.yaml"))
+ .build());
+ beforeAll();
+ }
+
+ @AfterAll
+ static void stop() {
+ afterAll();
+ }
+}
diff --git a/examples/dbclient/jdbc/src/test/resources/application-h2-test.yaml b/examples/dbclient/jdbc/src/test/resources/application-h2-test.yaml
new file mode 100644
index 000000000..f75ba554c
--- /dev/null
+++ b/examples/dbclient/jdbc/src/test/resources/application-h2-test.yaml
@@ -0,0 +1,72 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8079
+ host: 0.0.0.0
+
+tracing:
+ service: jdbc-db
+
+db:
+ source: jdbc
+ connection:
+ username: sa
+ password:
+ poolName: h2
+ initializationFailTimeout: -1
+ connectionTimeout: 2000
+ helidon:
+ pool-metrics:
+ 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."
+ health-check:
+ type: "query"
+ statementName: "health-check"
+ services:
+ tracing:
+ # would trace all statement names that start with select-
+ - statement-names: ["select-.*"]
+ # would trace all delete statements
+ - statement-types: ["DELETE"]
+ metrics:
+ - 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:
+ health-check: "SELECT 0"
+ # Insert new pokemon
+ create-table: "CREATE TABLE pokemons (name VARCHAR(64) NOT NULL PRIMARY KEY, type VARCHAR(32))"
+ insert1: "INSERT INTO pokemons VALUES(?, ?)"
+ insert2: "INSERT INTO pokemons VALUES(:name, :type)"
+ select-by-type: "SELECT * FROM pokemons WHERE type = ?"
+ select-one: "SELECT * FROM pokemons WHERE name = ?"
+ select-all: "SELECT * FROM pokemons"
+ select-for-update: "SELECT * FROM pokemons WHERE name = :name for UPDATE"
+ update: "UPDATE pokemons SET type = :type WHERE name = :name"
+ delete: "DELETE FROM pokemons WHERE name = ?"
diff --git a/examples/dbclient/jdbc/src/test/resources/application-mysql-test.yaml b/examples/dbclient/jdbc/src/test/resources/application-mysql-test.yaml
new file mode 100644
index 000000000..8bf828879
--- /dev/null
+++ b/examples/dbclient/jdbc/src/test/resources/application-mysql-test.yaml
@@ -0,0 +1,72 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8079
+ host: 0.0.0.0
+
+tracing:
+ service: jdbc-db
+
+db:
+ source: jdbc
+ connection:
+ username: user
+ password: changeit
+ poolName: mysql
+ initializationFailTimeout: -1
+ connectionTimeout: 2000
+ helidon:
+ pool-metrics:
+ 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."
+ health-check:
+ type: "query"
+ statementName: "health-check"
+ services:
+ tracing:
+ # would trace all statement names that start with select-
+ - statement-names: ["select-.*"]
+ # would trace all delete statements
+ - statement-types: ["DELETE"]
+ metrics:
+ - 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:
+ health-check: "SELECT 0"
+ # Insert new pokemon
+ create-table: "CREATE TABLE pokemons (name VARCHAR(64) NOT NULL PRIMARY KEY, type VARCHAR(32))"
+ insert1: "INSERT INTO pokemons VALUES(?, ?)"
+ insert2: "INSERT INTO pokemons VALUES(:name, :type)"
+ select-by-type: "SELECT * FROM pokemons WHERE type = ?"
+ select-one: "SELECT * FROM pokemons WHERE name = ?"
+ select-all: "SELECT * FROM pokemons"
+ select-for-update: "SELECT * FROM pokemons WHERE name = :name for UPDATE"
+ update: "UPDATE pokemons SET type = :type WHERE name = :name"
+ delete: "DELETE FROM pokemons WHERE name = ?"
diff --git a/examples/dbclient/jdbc/src/test/resources/application-oracle-test.yaml b/examples/dbclient/jdbc/src/test/resources/application-oracle-test.yaml
new file mode 100644
index 000000000..dd713c055
--- /dev/null
+++ b/examples/dbclient/jdbc/src/test/resources/application-oracle-test.yaml
@@ -0,0 +1,74 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8079
+ host: 0.0.0.0
+
+tracing:
+ service: jdbc-db
+
+db:
+ source: jdbc
+ connection:
+ username: "system"
+ password: "oracle"
+ initializationFailTimeout: -1
+ connectionTimeout: 2000
+ helidon:
+ pool-metrics:
+ 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."
+ health-check:
+ type: "query"
+ statementName: "health-check"
+ services:
+ tracing:
+ # would trace all statement names that start with select-
+ - statement-names: ["select-.*"]
+ # would trace all delete statements
+ - statement-types: ["DELETE"]
+ metrics:
+ - 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:
+ # Health check query statement for MySQL and H2 databases
+ # health-check: "SELECT 0"
+ # Health check query statement for Oracle database
+ health-check: "SELECT 1 FROM DUAL"
+ # Insert new pokemon
+ create-table: "CREATE TABLE pokemons (name VARCHAR(64) NOT NULL PRIMARY KEY, type VARCHAR(32))"
+ insert1: "INSERT INTO pokemons VALUES(?, ?)"
+ insert2: "INSERT INTO pokemons VALUES(:name, :type)"
+ select-by-type: "SELECT * FROM pokemons WHERE type = ?"
+ select-one: "SELECT * FROM pokemons WHERE name = ?"
+ select-all: "SELECT * FROM pokemons"
+ select-for-update: "SELECT * FROM pokemons WHERE name = :name for UPDATE"
+ update: "UPDATE pokemons SET type = :type WHERE name = :name"
+ delete: "DELETE FROM pokemons WHERE name = ?"
diff --git a/examples/dbclient/mongodb/README.md b/examples/dbclient/mongodb/README.md
new file mode 100644
index 000000000..45bebc5ec
--- /dev/null
+++ b/examples/dbclient/mongodb/README.md
@@ -0,0 +1,50 @@
+# Helidon DB Client mongoDB Example
+
+This example shows how to run Helidon DB over mongoDB.
+
+
+## Build
+
+```shell
+mvn package
+```
+
+## Run
+
+This example requires a mongoDB database, start it using docker:
+
+```shell
+docker run --rm --name mongo -p 27017:27017 mongo
+```
+
+Then run the application:
+
+```shell
+java -jar target/helidon-examples-dbclient-mongodb.jar
+```
+
+
+## Exercise
+
+The application has the following endpoints:
+
+- http://localhost:8079/db - the main business endpoint (see `curl` commands below)
+- http://localhost:8079/metrics - the metrics endpoint (query adds application metrics)
+- http://localhost:8079/health - has a custom database health check
+
+Application also connects to zipkin on default address.
+The query operation adds database trace.
+
+`curl` commands:
+
+- `curl http://localhost:8079/db` - list all Pokemon in the database
+- `curl -i -X PUT -H 'Content-type: application/json' -d '{"name":"Squirtle","type":"water"}' http://localhost:8079/db` - add a new pokemon
+- `curl http://localhost:8079/db/Squirtle` - get a single pokemon
+- `curl -i -X DELETE http://localhost:8079/db/Squirtle` - delete a single pokemon
+- `curl -i -X DELETE http://localhost:8079/db` - delete all pokemon
+
+The application also supports update and delete - see `PokemonService.java` for bound endpoints.
+
+---
+
+Pokémon, and Pokémon character names are trademarks of Nintendo.
diff --git a/examples/dbclient/mongodb/pom.xml b/examples/dbclient/mongodb/pom.xml
new file mode 100644
index 000000000..0701e6270
--- /dev/null
+++ b/examples/dbclient/mongodb/pom.xml
@@ -0,0 +1,155 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+
+ helidon-examples-dbclient-mongodb
+ 1.0.0-SNAPSHOT
+ Helidon Examples DB Client MongoDB
+
+
+ io.helidon.examples.dbclient.mongo.MongoDbExampleMain
+
+
+
+
+ io.helidon.common
+ helidon-common
+
+
+ io.helidon.common
+ helidon-common-mapper
+
+
+ io.helidon.dbclient
+ helidon-dbclient-mongodb
+
+
+ io.helidon.dbclient
+ helidon-dbclient-tracing
+
+
+ io.helidon.dbclient
+ helidon-dbclient-metrics
+
+
+ io.helidon.dbclient
+ helidon-dbclient-health
+
+
+ io.helidon.dbclient
+ helidon-dbclient-jsonp
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-tracing
+
+
+ io.helidon.metrics
+ helidon-metrics-api
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-metrics
+ runtime
+
+
+ io.helidon.metrics
+ helidon-metrics-system-meters
+ runtime
+
+
+ io.helidon.tracing
+ helidon-tracing
+
+
+ io.helidon.tracing.providers
+ helidon-tracing-providers-zipkin
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.examples.dbclient
+ helidon-examples-dbclient-common
+ ${project.version}
+
+
+ io.helidon.logging
+ helidon-logging-jul
+ runtime
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+ org.testcontainers
+ mongodb
+ test
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+
+
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
new file mode 100644
index 000000000..de853eb9c
--- /dev/null
+++ b/examples/dbclient/mongodb/src/main/java/io/helidon/examples/dbclient/mongo/MongoDbExampleMain.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.dbclient.mongo;
+
+import io.helidon.config.Config;
+import io.helidon.dbclient.DbClient;
+import io.helidon.dbclient.DbStatementType;
+import io.helidon.dbclient.metrics.DbClientMetrics;
+import io.helidon.dbclient.tracing.DbClientTracing;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.WebServerConfig;
+import io.helidon.webserver.http.HttpRouting;
+
+/**
+ * Simple Hello World rest application.
+ */
+public final class MongoDbExampleMain {
+
+ /**
+ * Cannot be instantiated.
+ */
+ private MongoDbExampleMain() {
+ }
+
+ /**
+ * Application main entry point.
+ *
+ * @param args command line arguments.
+ */
+ public static void main(String[] args) {
+ startServer();
+ }
+
+ /**
+ * Start the server.
+ *
+ * @return the created {@link WebServer} instance
+ */
+ static WebServer startServer() {
+
+ // load logging configuration
+ LogConfig.configureRuntime();
+
+ WebServer server = setupServer(WebServer.builder());
+
+ System.out.println("WEB server is up! http://localhost:" + server.port() + "/");
+ return server;
+ }
+
+ static WebServer setupServer(WebServerConfig.Builder builder) {
+ // By default, this will pick up application.yaml from the classpath
+ Config config = Config.create();
+
+ return builder.routing(routing -> routing(routing, config))
+ .config(config.get("server"))
+ .build()
+ .start();
+ }
+
+ /**
+ * Setup routing.
+ *
+ * @param config configuration of this server
+ */
+ private static void routing(HttpRouting.Builder routing, Config config) {
+ Config dbConfig = config.get("db");
+
+ DbClient dbClient = DbClient.builder(dbConfig)
+ // add an interceptor to named statement(s)
+ .addService(DbClientMetrics.counter().statementNames("select-all", "select-one"))
+ // add an interceptor to statement type(s)
+ .addService(DbClientMetrics.timer()
+ .statementTypes(DbStatementType.DELETE, DbStatementType.UPDATE, DbStatementType.INSERT))
+ // add an interceptor to all statements
+ .addService(DbClientTracing.create())
+ .build();
+
+ routing.register("/db", new PokemonService(dbClient));
+ }
+}
diff --git a/examples/dbclient/mongodb/src/main/java/io/helidon/examples/dbclient/mongo/PokemonService.java b/examples/dbclient/mongodb/src/main/java/io/helidon/examples/dbclient/mongo/PokemonService.java
new file mode 100644
index 000000000..be1c7b251
--- /dev/null
+++ b/examples/dbclient/mongodb/src/main/java/io/helidon/examples/dbclient/mongo/PokemonService.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.dbclient.mongo;
+
+import io.helidon.dbclient.DbClient;
+import io.helidon.examples.dbclient.common.AbstractPokemonService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+/**
+ * A simple service to greet you. Examples:
+ *
+ * Get default greeting message:
+ * curl -X GET {@code http://localhost:8080/greet}
+ *
+ * Get greeting message for Joe:
+ * curl -X GET {@code http://localhost:8080/greet/Joe}
+ *
+ * Change greeting
+ * curl -X PUT {@code http://localhost:8080/greet/greeting/Hola}
+ *
+ * The message is returned as a JSON object
+ */
+
+public class PokemonService extends AbstractPokemonService {
+
+ PokemonService(DbClient dbClient) {
+ super(dbClient);
+ }
+
+ @Override
+ protected void deleteAllPokemons(ServerRequest req, ServerResponse res) {
+ long count = dbClient().execute().createNamedDelete("delete-all")
+ .execute();
+ res.send("Deleted: " + count + " values");
+ }
+}
diff --git a/examples/dbclient/mongodb/src/main/java/io/helidon/examples/dbclient/mongo/package-info.java b/examples/dbclient/mongodb/src/main/java/io/helidon/examples/dbclient/mongo/package-info.java
new file mode 100644
index 000000000..699f9ba3e
--- /dev/null
+++ b/examples/dbclient/mongodb/src/main/java/io/helidon/examples/dbclient/mongo/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Quick start demo application.
+ */
+package io.helidon.examples.dbclient.mongo;
diff --git a/examples/dbclient/mongodb/src/main/resources/application.yaml b/examples/dbclient/mongodb/src/main/resources/application.yaml
new file mode 100644
index 000000000..d0cd67c62
--- /dev/null
+++ b/examples/dbclient/mongodb/src/main/resources/application.yaml
@@ -0,0 +1,76 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8079
+ host: 0.0.0.0
+
+tracing:
+ service: "mongo-db"
+
+# docker run --rm --name mongo -p 27017:27017 mongo
+db:
+ source: "mongoDb"
+ connection:
+ url: "mongodb://127.0.0.1:27017/pokemon"
+ health-check:
+ type: "query"
+ statementName: "health-check"
+ statements:
+ # Health check statement. HealthCheck statement type must be query.
+ health-check: '{
+ "operation": "command",
+ "query": { ping: 1 }
+ }'
+ # Insert operation contains collection name, operation type and data to be inserted.
+ # Name variable is stored as MongoDB primary key attribute _id
+ insert2: '{
+ "collection": "pokemons",
+ "value": {
+ "_id": $name,
+ "type": $type
+ }
+ }'
+ select-all: '{
+ "collection": "pokemons",
+ "query": {}
+ }'
+ select-one: '{
+ "collection": "pokemons",
+ "query": {
+ "_id": ?
+ }
+ }'
+ delete-all: '{
+ "collection": "pokemons",
+ "operation": "delete"
+ }'
+ update: '{
+ "collection": "pokemons",
+ "query": {
+ "_id": $name
+ },
+ "value": {
+ $set: { "type": $type }
+ }
+ }'
+ delete: '{
+ "collection": "pokemons",
+ "query": {
+ "_id": ?
+ }
+ }'
+
diff --git a/examples/dbclient/mongodb/src/main/resources/logging.properties b/examples/dbclient/mongodb/src/main/resources/logging.properties
new file mode 100644
index 000000000..550b8d9a4
--- /dev/null
+++ b/examples/dbclient/mongodb/src/main/resources/logging.properties
@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# Global default logging level. Can be overridden 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
+io.helidon.logging.jul.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
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
diff --git a/examples/dbclient/mongodb/src/test/java/io/helidon/examples/dbclient/mongo/MainIT.java b/examples/dbclient/mongodb/src/test/java/io/helidon/examples/dbclient/mongo/MainIT.java
new file mode 100644
index 000000000..18b89b947
--- /dev/null
+++ b/examples/dbclient/mongodb/src/test/java/io/helidon/examples/dbclient/mongo/MainIT.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2023, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.dbclient.mongo;
+
+import java.util.List;
+import java.util.Map;
+
+import io.helidon.config.Config;
+import io.helidon.http.Status;
+import io.helidon.http.media.jsonb.JsonbSupport;
+import io.helidon.http.media.jsonp.JsonpSupport;
+import io.helidon.webclient.api.ClientResponseTyped;
+import io.helidon.webclient.api.WebClient;
+import io.helidon.webserver.WebServer;
+
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.MongoDBContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+@Testcontainers(disabledWithoutDocker = true)
+public class MainIT {
+
+ @Container
+ static final MongoDBContainer container = new MongoDBContainer("mongo")
+ .withExposedPorts(27017);
+
+ private static final JsonBuilderFactory JSON_FACTORY = Json.createBuilderFactory(Map.of());
+ private static final String CONNECTION_URL_KEY = "db.connection.url";
+
+ private static WebServer server;
+ private static WebClient client;
+
+ @BeforeAll
+ static void beforeAll() {
+ String url = String.format("mongodb://127.0.0.1:%s/pokemon", container.getMappedPort(27017));
+ System.setProperty(CONNECTION_URL_KEY, url);
+ server = MongoDbExampleMain.setupServer(WebServer.builder());
+ client = WebClient.create(config -> config.baseUri("http://localhost:" + server.port())
+ .addMediaSupport(JsonbSupport.create(Config.create()))
+ .addMediaSupport(JsonpSupport.create()));
+ }
+
+ @AfterAll
+ static void afterAll() {
+ if (server != null && server.isRunning()) {
+ server.stop();
+ }
+ System.clearProperty(CONNECTION_URL_KEY);
+ }
+
+ @Test
+ void testListAndDeleteAllPokemons() {
+ List names = listAllPokemons();
+ assertThat(names.isEmpty(), is(true));
+
+ String endpoint = String.format("/db/%s/type/%s", "Raticate", 1);
+ ClientResponseTyped response = client.post(endpoint).request(String.class);
+ assertThat(response.status(), is(Status.OK_200));
+
+ names = listAllPokemons();
+ assertThat(names.size(), is(1));
+ assertThat(names.getFirst(), is("Raticate"));
+
+ response = client.delete("/db").request(String.class);
+ assertThat(response.status(), is(Status.OK_200));
+
+ names = listAllPokemons();
+ assertThat(names.isEmpty(), is(true));
+ }
+
+ @Test
+ void testAddUpdateDeletePokemon() {
+ ClientResponseTyped response;
+ ClientResponseTyped jsonResponse;
+ JsonObject pokemon = JSON_FACTORY.createObjectBuilder()
+ .add("type", 1)
+ .add("name", "Raticate")
+ .build();
+
+ // Add new pokemon
+ response = client.put("/db").submit(pokemon, String.class);
+ assertThat(response.entity(), is("Inserted: 1 values"));
+
+ // Get the new pokemon added
+ jsonResponse = client.get("/db/Raticate").request(JsonObject.class);
+ assertThat(jsonResponse.status(), is(Status.OK_200));
+ assertThat(jsonResponse.entity().getString("_id"), is("Raticate"));
+ assertThat(jsonResponse.entity().getString("type"), is("1"));
+
+ // Update pokemon
+ response = client.put("/db/Raticate/type/2").request(String.class);
+ assertThat(response.status(), is(Status.OK_200));
+
+ // Verify updated pokemon
+ jsonResponse = client.get("/db/Raticate").request(JsonObject.class);
+ assertThat(jsonResponse.status(), is(Status.OK_200));
+ assertThat(jsonResponse.entity().getString("_id"), is("Raticate"));
+ assertThat(jsonResponse.entity().getString("type"), is("2"));
+
+ // Delete Pokemon
+ response = client.delete("/db/Raticate").request(String.class);
+ assertThat(response.status(), is(Status.OK_200));
+
+ // Verify pokemon is correctly deleted
+ response = client.get("/db/Raticate").request(String.class);
+ assertThat(response.status(), is(Status.NOT_FOUND_404));
+ }
+
+ private List listAllPokemons() {
+ ClientResponseTyped response = client.get("/db").request(JsonArray.class);
+ assertThat(response.status(), is(Status.OK_200));
+ return response.entity().stream().map(e -> e.asJsonObject().getString("_id")).toList();
+ }
+}
diff --git a/examples/dbclient/pokemons/README.md b/examples/dbclient/pokemons/README.md
new file mode 100644
index 000000000..8d76e9913
--- /dev/null
+++ b/examples/dbclient/pokemons/README.md
@@ -0,0 +1,141 @@
+# Helidon DB Client Pokémon Example with JDBC
+
+This example shows how to run Helidon DB Client over JDBC.
+
+Application provides REST service endpoint with CRUD operations on Pokémons
+database.
+
+## Database
+
+Database model contains two tables:
+
+**Types**
+
+| Column | Type | Integrity |
+|--------|---------|-------------|
+| id | integer | Primary key |
+| name | varchar | |
+
+**Pokemons**
+
+| Column | Type | Integrity |
+|---------|---------|-------------|
+| id | integer | Primary key |
+| name | varchar | |
+| id_type | integer | Type(id) |
+
+with 1:N relationship between *Types* and *Pokémons*
+
+Examples are given for H2, Oracle, or MySQL databases (note that MySQL is currently not supported for GraalVM native image)
+
+To switch between JDBC drivers:
+
+- Uncomment the appropriate dependency in `pom.xml`
+- Uncomment the configuration section in `application.yaml` and comment out the current one
+
+## Build
+
+To build a jar file
+```shell
+mvn package
+```
+
+To build a native image (supported only with Oracle, MongoDB, or H2 databases)
+```shell
+mvn package -Pnative-image
+```
+
+## Database
+This example can run with any JDBC supported database.
+In the `pom.xml` and `application.yaml` we provide configuration needed for Oracle database, MySQL and H2 database.
+Start your database before running this example.
+
+Example docker commands to start databases in temporary containers:
+
+Oracle:
+```shell
+docker run --rm --name xe -p 1521:1521 -p 8888:8080 wnameless/oracle-xe-11g-r2
+```
+For details on an Oracle Docker image, see https://github.com/oracle/docker-images/tree/master/OracleDatabase/SingleInstance
+
+H2:
+```shell
+docker run --rm --name h2 -p 9092:9082 -p 8082:8082 nemerosa/h2
+```
+For details, see http://www.h2database.com/html/cheatSheet.html
+
+MySQL:
+```shell
+docker run --rm --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root \
+ -e MYSQL_DATABASE=pokemon -e MYSQL_USER=user -e MYSQL_PASSWORD=changeit mysql:5.7
+```
+
+
+## Run
+
+Then run the `io.helidon.examples.dbclient.pokemons.Main` class:
+```shell
+java -jar target/helidon-examples-dbclient-pokemons.jar
+```
+
+Or run the native image:
+```shell
+./target/helidon-examples-dbclient-pokemons
+```
+
+### Run with MongoDB
+
+It's possible to run example with MongoDB database. Start it using docker:
+```shell
+docker run --rm --name mongo -p 27017:27017 mongo
+```
+
+Then run the `io.helidon.examples.dbclient.pokemons.Main` class with `mongo` argument:
+```shell
+java -jar target/helidon-examples-dbclient-pokemons.jar mongo
+```
+
+## Test Example
+
+The application has the following endpoints:
+
+- http://localhost:8080/db - the main business endpoint (see `curl` commands below)
+- http://localhost:8080/metrics - the metrics endpoint (query adds application metrics)
+- http://localhost:8080/health - has a custom database health check
+
+Application also connects to zipkin on default address.
+The query operation adds database trace.
+
+```shell
+# List all Pokémon
+curl http://localhost:8080/db/pokemon
+
+# List all Pokémon types
+curl http://localhost:8080/db/type
+
+# Get a single Pokémon by id
+curl http://localhost:8080/db/pokemon/2
+
+# Get a single Pokémon by name
+curl http://localhost:8080/db/pokemon/name/Squirtle
+
+# Add a new Pokémon Rattata
+curl -i -X POST -H 'Content-type: application/json' -d '{"id":7,"name":"Rattata","idType":1}' http://localhost:8080/db/pokemon
+
+# Rename Pokémon with id 7 to Raticate
+curl -i -X PUT -H 'Content-type: application/json' -d '{"id":7,"name":"Raticate","idType":2}' http://localhost:8080/db/pokemon
+
+# Delete Pokémon with id 7
+curl -i -X DELETE http://localhost:8080/db/pokemon/7
+```
+
+### Proxy
+
+Make sure that `localhost` is not being accessed trough proxy when proxy is configured on your system:
+```shell
+export NO_PROXY='localhost'
+```
+
+---
+
+Pokémon, and Pokémon character names are trademarks of Nintendo.
diff --git a/examples/dbclient/pokemons/pom.xml b/examples/dbclient/pokemons/pom.xml
new file mode 100644
index 000000000..0de324ad1
--- /dev/null
+++ b/examples/dbclient/pokemons/pom.xml
@@ -0,0 +1,209 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+
+ helidon-examples-dbclient-pokemons
+ 1.0.0-SNAPSHOT
+ Helidon Examples DB Client: Pokemons Database
+
+
+ io.helidon.examples.dbclient.pokemons.Main
+
+
+
+
+ io.helidon.tracing
+ helidon-tracing
+
+
+ io.helidon.tracing.providers
+ helidon-tracing-providers-zipkin
+
+
+ io.helidon.dbclient
+ helidon-dbclient-jdbc
+
+
+ io.helidon.dbclient
+ helidon-dbclient-mongodb
+
+
+ io.helidon.dbclient
+ helidon-dbclient-tracing
+
+
+ io.helidon.dbclient
+ helidon-dbclient-metrics
+
+
+ io.helidon.dbclient
+ helidon-dbclient-metrics-hikari
+
+
+ io.helidon.dbclient
+ helidon-dbclient-health
+
+
+ io.helidon.dbclient
+ helidon-dbclient-jsonp
+
+
+ io.helidon.integrations.db
+ ojdbc
+
+
+ io.helidon.integrations.db
+ h2
+
+
+ org.slf4j
+ slf4j-jdk14
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-metrics
+ runtime
+
+
+ io.helidon.metrics
+ helidon-metrics-system-meters
+ runtime
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-health
+
+
+ io.helidon.health
+ helidon-health-checks
+
+
+ io.helidon.http.media
+ helidon-http-media-jsonp
+
+
+ io.helidon.http.media
+ helidon-http-media-jsonb
+
+
+ org.mongodb
+ mongodb-driver-sync
+
+
+ io.helidon.logging
+ helidon-logging-jul
+ runtime
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+ org.testcontainers
+ mongodb
+ test
+
+
+ org.testcontainers
+ mysql
+ test
+
+
+ org.testcontainers
+ oracle-xe
+ test
+
+
+ io.helidon.webclient
+ helidon-webclient
+ test
+
+
+ com.mysql
+ mysql-connector-j
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+
+ false
+
+
+
+
+
+
+
+
diff --git a/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/Main.java b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/Main.java
new file mode 100644
index 000000000..d3e83ca89
--- /dev/null
+++ b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/Main.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.dbclient.pokemons;
+
+import io.helidon.common.context.Contexts;
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+import io.helidon.dbclient.DbClient;
+import io.helidon.dbclient.health.DbClientHealthCheck;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.WebServerConfig;
+import io.helidon.webserver.http.HttpRouting;
+import io.helidon.webserver.observe.ObserveFeature;
+import io.helidon.webserver.observe.health.HealthObserver;
+
+/**
+ * Simple Hello World rest application.
+ */
+public final class Main {
+
+ /**
+ * MongoDB configuration. Default configuration file {@code application.yaml} contains JDBC configuration.
+ */
+ private static final String MONGO_CFG = "mongo.yaml";
+
+ /**
+ * Whether MongoDB support is selected.
+ */
+ private static boolean mongo;
+
+ /**
+ * Cannot be instantiated.
+ */
+ private Main() {
+ }
+
+ /**
+ * Application main entry point.
+ *
+ * @param args Command line arguments. Run with MongoDB support when 1st argument is mongo, run with JDBC support otherwise.
+ */
+ public static void main(final String[] args) {
+ if (args != null && args.length > 0 && args[0] != null && "mongo".equalsIgnoreCase(args[0])) {
+ System.out.println("MongoDB database selected");
+ mongo = true;
+ } else {
+ System.out.println("JDBC database selected");
+ mongo = false;
+ }
+ startServer();
+ }
+
+ private static void startServer() {
+
+ // load logging configuration
+ LogConfig.configureRuntime();
+
+ // By default, this will pick up application.yaml from the classpath
+ Config config = mongo ? Config.create(ConfigSources.classpath(MONGO_CFG)) : Config.create();
+ Config.global(config);
+
+ WebServer server = setupServer(WebServer.builder());
+
+ System.out.println("WEB server is up! http://localhost:" + server.port() + "/");
+ }
+
+ static WebServer setupServer(WebServerConfig.Builder builder) {
+
+ Config config = Config.global();
+ // Client services are added through a service loader - see mongoDB example for explicit services
+ DbClient dbClient = DbClient.create(config.get("db"));
+ Contexts.globalContext().register(dbClient);
+
+ ObserveFeature observe = ObserveFeature.builder()
+ .config(config.get("server.features.observe"))
+ .addObserver(HealthObserver.builder()
+ .addCheck(DbClientHealthCheck.create(dbClient, config.get("db.health-check")))
+ .build())
+ .build();
+ return builder.config(config.get("server"))
+ .addFeature(observe)
+ .routing(Main::routing)
+ .build()
+ .start();
+ }
+
+ /**
+ * Updates HTTP Routing.
+ */
+ static void routing(HttpRouting.Builder routing) {
+ routing.register("/db", new PokemonService());
+ }
+}
diff --git a/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/Pokemon.java b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/Pokemon.java
new file mode 100644
index 000000000..691dda574
--- /dev/null
+++ b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/Pokemon.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.dbclient.pokemons;
+
+/**
+ * POJO representing Pokémon.
+ *
+ * @param id id of the beast
+ * @param name name of the beast
+ * @param idType id of the beast type
+ */
+public record Pokemon(int id, String name, int idType) {
+}
diff --git a/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonMapper.java b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonMapper.java
new file mode 100644
index 000000000..1fc9fa8c4
--- /dev/null
+++ b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonMapper.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.dbclient.pokemons;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import io.helidon.dbclient.DbColumn;
+import io.helidon.dbclient.DbMapper;
+import io.helidon.dbclient.DbRow;
+
+/**
+ * Maps database statements to {@link io.helidon.examples.dbclient.pokemons.Pokemon} class.
+ */
+public class PokemonMapper implements DbMapper {
+
+ @Override
+ public Pokemon read(DbRow row) {
+ DbColumn id = row.column("id");
+ DbColumn name = row.column("name");
+ DbColumn type = row.column("idType");
+ return new Pokemon(id.get(Integer.class), name.get(String.class), type.get(Integer.class));
+ }
+
+ @Override
+ public Map toNamedParameters(Pokemon value) {
+ Map map = new HashMap<>(3);
+ map.put("id", value.id());
+ map.put("name", value.name());
+ map.put("idType", value.idType());
+ return map;
+ }
+
+ @Override
+ public List toIndexedParameters(Pokemon value) {
+ List list = new ArrayList<>(3);
+ list.add(value.id());
+ list.add(value.name());
+ list.add(value.idType());
+ return list;
+ }
+}
diff --git a/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonMapperProvider.java b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonMapperProvider.java
new file mode 100644
index 000000000..12d2a837f
--- /dev/null
+++ b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonMapperProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.dbclient.pokemons;
+
+import java.util.Optional;
+
+import io.helidon.common.Weight;
+import io.helidon.dbclient.DbMapper;
+import io.helidon.dbclient.spi.DbMapperProvider;
+
+/**
+ * Provides pokemon mappers.
+ */
+@Weight(100)
+public class PokemonMapperProvider implements DbMapperProvider {
+ private static final PokemonMapper MAPPER = new PokemonMapper();
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Optional> mapper(Class type) {
+ if (type.equals(Pokemon.class)) {
+ return Optional.of((DbMapper) MAPPER);
+ }
+ return Optional.empty();
+ }
+}
diff --git a/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonService.java b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonService.java
new file mode 100644
index 000000000..7454b1eaf
--- /dev/null
+++ b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonService.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.dbclient.pokemons;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.Map;
+
+import io.helidon.common.context.Contexts;
+import io.helidon.common.media.type.MediaTypes;
+import io.helidon.config.Config;
+import io.helidon.dbclient.DbClient;
+import io.helidon.dbclient.DbExecute;
+import io.helidon.dbclient.DbTransaction;
+import io.helidon.http.BadRequestException;
+import io.helidon.http.NotFoundException;
+import io.helidon.http.Status;
+import io.helidon.webserver.http.Handler;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonArrayBuilder;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonReader;
+import jakarta.json.JsonValue;
+
+/**
+ * An {@link HttpService} that uses {@link DbClient}.
+ */
+public class PokemonService implements HttpService {
+
+ private static final Logger LOGGER = System.getLogger(PokemonService.class.getName());
+ private static final JsonBuilderFactory JSON_FACTORY = Json.createBuilderFactory(Map.of());
+
+ private final DbClient dbClient;
+ private final boolean initSchema;
+ private final boolean initData;
+
+ /**
+ * Create a new Pokémon service with a DB client.
+ */
+ PokemonService() {
+ Config config = Config.global().get("db");
+ this.dbClient = Contexts.globalContext()
+ .get(DbClient.class)
+ .orElseGet(() -> DbClient.create(config));
+
+ initSchema = config.get("init-schema").asBoolean().orElse(true);
+ initData = config.get("init-data").asBoolean().orElse(true);
+ init();
+ }
+
+ private void init() {
+ if (initSchema) {
+ initSchema();
+ }
+ if (initData) {
+ initData();
+ }
+ }
+
+ private void initSchema() {
+ DbExecute exec = dbClient.execute();
+ try {
+ exec.namedDml("create-types");
+ exec.namedDml("create-pokemons");
+ } catch (Exception ex1) {
+ LOGGER.log(Level.WARNING, "Could not create tables", ex1);
+ try {
+ deleteData();
+ } catch (Exception ex2) {
+ LOGGER.log(Level.WARNING, "Could not delete tables", ex2);
+ }
+ }
+ }
+
+ private void initData() {
+ DbTransaction tx = dbClient.transaction();
+ try {
+ initTypes(tx);
+ initPokemons(tx);
+ tx.commit();
+ } catch (Throwable t) {
+ tx.rollback();
+ throw t;
+ }
+ }
+
+ private static void initTypes(DbExecute exec) {
+ try (JsonReader reader = Json.createReader(PokemonService.class.getResourceAsStream("/pokemon-types.json"))) {
+ JsonArray types = reader.readArray();
+ for (JsonValue typeValue : types) {
+ JsonObject type = typeValue.asJsonObject();
+ exec.namedInsert("insert-type",
+ type.getInt("id"),
+ type.getString("name"));
+ }
+ }
+ }
+
+ private static void initPokemons(DbExecute exec) {
+ try (JsonReader reader = Json.createReader(PokemonService.class.getResourceAsStream("/pokemons.json"))) {
+ JsonArray pokemons = reader.readArray();
+ for (JsonValue pokemonValue : pokemons) {
+ JsonObject pokemon = pokemonValue.asJsonObject();
+ exec.namedInsert("insert-pokemon",
+ pokemon.getInt("id"),
+ pokemon.getString("name"),
+ pokemon.getInt("idType"));
+ }
+ }
+ }
+
+ private void deleteData() {
+ DbTransaction tx = dbClient.transaction();
+ try {
+ tx.namedDelete("delete-all-pokemons");
+ tx.namedDelete("delete-all-types");
+ tx.commit();
+ } catch (Throwable t) {
+ tx.rollback();
+ throw t;
+ }
+ }
+
+ @Override
+ public void routing(HttpRules rules) {
+ rules.get("/", this::index)
+ // List all types
+ .get("/type", this::listTypes)
+ // List all Pokémon
+ .get("/pokemon", this::listPokemons)
+ // Get Pokémon by name
+ .get("/pokemon/name/{name}", this::getPokemonByName)
+ // Get Pokémon by ID
+ .get("/pokemon/{id}", this::getPokemonById)
+ // Create new Pokémon
+ .post("/pokemon", Handler.create(Pokemon.class, this::insertPokemon))
+ // Update name of existing Pokémon
+ .put("/pokemon", Handler.create(Pokemon.class, this::updatePokemon))
+ // Delete Pokémon by ID including type relation
+ .delete("/pokemon/{id}", this::deletePokemonById);
+ }
+
+ /**
+ * Return index page.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void index(ServerRequest request, ServerResponse response) {
+ response.headers().contentType(MediaTypes.TEXT_PLAIN);
+ response.send("""
+ Pokemon JDBC Example:
+ GET /type - List all pokemon types
+ GET /pokemon - List all pokemons
+ GET /pokemon/{id} - Get pokemon by id
+ GET /pokemon/name/{name} - Get pokemon by name
+ POST /pokemon - Insert new pokemon:
+ {"id":,"name":,"type":}
+ PUT /pokemon - Update pokemon
+ {"id":,"name":,"type":}
+ DELETE /pokemon/{id} - Delete pokemon with specified id
+ """);
+ }
+
+ /**
+ * Return JsonArray with all stored Pokémon.
+ * Pokémon object contains list of all type names.
+ * This method is abstract because implementation is DB dependent.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void listTypes(ServerRequest request, ServerResponse response) {
+ JsonArray jsonArray = dbClient.execute()
+ .namedQuery("select-all-types")
+ .map(row -> row.as(JsonObject.class))
+ .collect(JSON_FACTORY::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::addAll)
+ .build();
+ response.send(jsonArray);
+ }
+
+ /**
+ * Return JsonArray with all stored Pokémon.
+ * Pokémon object contains list of all type names.
+ * This method is abstract because implementation is DB dependent.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void listPokemons(ServerRequest request, ServerResponse response) {
+ JsonArray jsonArray = dbClient.execute().namedQuery("select-all-pokemons")
+ .map(row -> row.as(JsonObject.class))
+ .collect(JSON_FACTORY::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::addAll)
+ .build();
+ response.send(jsonArray);
+ }
+
+ /**
+ * Get a single Pokémon by id.
+ *
+ * @param request server request
+ * @param response server response
+ */
+ private void getPokemonById(ServerRequest request, ServerResponse response) {
+ int pokemonId = Integer.parseInt(request.path()
+ .pathParameters()
+ .get("id"));
+
+ response.send(dbClient.execute().createNamedGet("select-pokemon-by-id")
+ .addParam("id", pokemonId)
+ .execute()
+ .orElseThrow(() -> new NotFoundException("Pokemon " + pokemonId + " not found"))
+ .as(JsonObject.class));
+ }
+
+ /**
+ * Get a single Pokémon by name.
+ *
+ * @param request server request
+ * @param response server response
+ */
+ private void getPokemonByName(ServerRequest request, ServerResponse response) {
+ String pokemonName = request.path().pathParameters().get("name");
+ response.send(dbClient.execute().namedGet("select-pokemon-by-name", pokemonName)
+ .orElseThrow(() -> new NotFoundException("Pokemon " + pokemonName + " not found"))
+ .as(JsonObject.class));
+ }
+
+ /**
+ * Insert new Pokémon with specified name.
+ *
+ * @param pokemon request entity
+ * @param response the server response
+ */
+ private void insertPokemon(Pokemon pokemon, ServerResponse response) {
+ long count = dbClient.execute().createNamedInsert("insert-pokemon")
+ .indexedParam(pokemon)
+ .execute();
+ response.status(Status.CREATED_201)
+ .send("Inserted: " + count + " values\n");
+ }
+
+ /**
+ * Update a Pokémon.
+ * Uses a transaction.
+ *
+ * @param pokemon request entity
+ * @param response the server response
+ */
+ private void updatePokemon(Pokemon pokemon, ServerResponse response) {
+ long count = dbClient.execute().createNamedUpdate("update-pokemon-by-id")
+ .namedParam(pokemon)
+ .execute();
+ response.send("Updated: " + count + " values\n");
+ }
+
+ /**
+ * Delete Pokémon with specified id (key).
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void deletePokemonById(ServerRequest request, ServerResponse response) {
+ int id = request.path()
+ .pathParameters()
+ .first("id").map(Integer::parseInt)
+ .orElseThrow(() -> new BadRequestException("No pokemon id"));
+ dbClient.execute().createNamedDelete("delete-pokemon-by-id")
+ .addParam("id", id)
+ .execute();
+ response.status(Status.NO_CONTENT_204)
+ .send();
+ }
+}
diff --git a/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/package-info.java b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/package-info.java
new file mode 100644
index 000000000..df1b4e759
--- /dev/null
+++ b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Quick start demo application.
+ */
+package io.helidon.examples.dbclient.pokemons;
diff --git a/examples/dbclient/pokemons/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbMapperProvider b/examples/dbclient/pokemons/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbMapperProvider
new file mode 100644
index 000000000..7f56b6a6d
--- /dev/null
+++ b/examples/dbclient/pokemons/src/main/resources/META-INF/services/io.helidon.dbclient.spi.DbMapperProvider
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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.examples.dbclient.pokemons.PokemonMapperProvider
diff --git a/examples/dbclient/pokemons/src/main/resources/application.yaml b/examples/dbclient/pokemons/src/main/resources/application.yaml
new file mode 100644
index 000000000..a1dbca78b
--- /dev/null
+++ b/examples/dbclient/pokemons/src/main/resources/application.yaml
@@ -0,0 +1,79 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8080
+ host: 0.0.0.0
+
+tracing:
+ service: jdbc-db
+
+# see README.md for details how to run databases in docker
+db:
+ source: jdbc
+ connection:
+ #
+ # Oracle configuration
+ #
+ url: "jdbc:oracle:thin:@localhost:1521/XE"
+ username: "system"
+ password: "oracle"
+ #
+ # MySQL configuration
+ #
+# url: jdbc:mysql://127.0.0.1:3306/pokemon?useSSL=false
+# username: user
+# password: password
+# poolName: "mysql"
+ #
+ # H2 configuration
+ #
+# url: "jdbc:h2:tcp://localhost:9092/~test"
+# username: h2
+# password: "${EMPTY}"
+# poolName: h2
+ initializationFailTimeout: -1
+ connectionTimeout: 2000
+ helidon:
+ pool-metrics:
+ 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."
+ services:
+ tracing:
+ - enabled: true
+ metrics:
+ - type: TIMER
+ health-check:
+ type: "query"
+ statementName: "health-check"
+ statements:
+ # Health check query statement for MySQL and H2 databases
+# health-check: "SELECT 0"
+ # Health check query statement for Oracle database
+ health-check: "SELECT 1 FROM DUAL"
+ create-types: "CREATE TABLE PokeTypes (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL)"
+ create-pokemons: "CREATE TABLE Pokemons (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL, id_type INTEGER NOT NULL REFERENCES PokeTypes(id))"
+ select-all-types: "SELECT id, name FROM PokeTypes"
+ select-all-pokemons: "SELECT id, name, id_type FROM Pokemons"
+ select-pokemon-by-id: "SELECT id, name, id_type FROM Pokemons WHERE id = :id"
+ select-pokemon-by-name: "SELECT id, name, id_type FROM Pokemons WHERE name = ?"
+ insert-type: "INSERT INTO PokeTypes(id, name) VALUES(?, ?)"
+ insert-pokemon: "INSERT INTO Pokemons(id, name, id_type) VALUES(?, ?, ?)"
+ update-pokemon-by-id: "UPDATE Pokemons SET name = :name, id_type = :idType WHERE id = :id"
+ delete-pokemon-by-id: "DELETE FROM Pokemons WHERE id = :id"
+ delete-all-types: "DELETE FROM PokeTypes"
+ delete-all-pokemons: "DELETE FROM Pokemons"
diff --git a/examples/dbclient/pokemons/src/main/resources/logging.properties b/examples/dbclient/pokemons/src/main/resources/logging.properties
new file mode 100644
index 000000000..550b8d9a4
--- /dev/null
+++ b/examples/dbclient/pokemons/src/main/resources/logging.properties
@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# Global default logging level. Can be overridden 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
+io.helidon.logging.jul.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
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
diff --git a/examples/dbclient/pokemons/src/main/resources/mongo.yaml b/examples/dbclient/pokemons/src/main/resources/mongo.yaml
new file mode 100644
index 000000000..104c637e2
--- /dev/null
+++ b/examples/dbclient/pokemons/src/main/resources/mongo.yaml
@@ -0,0 +1,115 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8080
+ host: 0.0.0.0
+
+tracing:
+ service: mongo-db
+
+db:
+ source: "mongoDb"
+ connection:
+ url: "mongodb://127.0.0.1:27017/pokemon"
+ init-schema: false
+ # Transactions are not supported
+ init-data: false
+ services:
+ tracing:
+ - enabled: true
+ health-check:
+ type: "query"
+ statementName: "health-check"
+ statements:
+ # Health check statement. HealthCheck statement type must be a query.
+ health-check: '{
+ "operation": "command",
+ "query": { ping: 1 }
+ }'
+ ## Create database schema
+ # Select all types
+ select-all-types: '{
+ "collection": "types",
+ "operation": "query",
+ "projection": { id: 1, name: 1, _id: 0 },
+ "query": {}
+ }'
+ # Select all pokemons without type information
+ select-all-pokemons: '{
+ "collection": "pokemons",
+ "operation": "query",
+ "projection": { id: 1, name: 1, id_type: 1, _id: 0 },
+ "query": {}
+ }'
+ # Select pokemon by id
+ select-pokemon-by-id: '{
+ "collection": "pokemons",
+ "operation": "query",
+ "projection": { id: 1, name: 1, id_type: 1, _id: 0 },
+ "query": { id: $id }
+ }'
+ # Select pokemon by name
+ select-pokemon-by-name: '{
+ "collection": "pokemons",
+ "operation": "query",
+ "projection": { id: 1, name: 1, id_type: 1, _id: 0 },
+ "query": { name: ? }
+ }'
+ # Insert records into database
+ insert-type: '{
+ "collection": "types",
+ "operation": "insert",
+ "value": {
+ "id": ?,
+ "name": ?
+ }
+ }'
+ insert-pokemon: '{
+ "collection": "pokemons",
+ "operation": "insert",
+ "value": {
+ "id": ?,
+ "name": ?,
+ "id_type": ?
+ }
+ }'
+ # Update name of pokemon specified by id
+ update-pokemon-by-id: '{
+ "collection": "pokemons",
+ "operation": "update",
+ "value":{ $set: { "name": $name, "id_type": $idType } },
+ "query": { id: $id }
+ }'
+ # Delete pokemon by id
+ delete-pokemon-by-id: '{
+ "collection": "pokemons",
+ "operation": "delete",
+ "query": { id: $id }
+ }'
+ # Delete all types
+ delete-all-types: '{
+ "collection": "types",
+ "operation": "delete",
+ "query": { }
+ }'
+ # Delete all pokemons
+ delete-all-pokemons: '{
+ "collection": "pokemons",
+ "operation": "delete",
+ "query": { }
+ }'
+
diff --git a/examples/dbclient/pokemons/src/main/resources/pokemon-types.json b/examples/dbclient/pokemons/src/main/resources/pokemon-types.json
new file mode 100644
index 000000000..646ec725b
--- /dev/null
+++ b/examples/dbclient/pokemons/src/main/resources/pokemon-types.json
@@ -0,0 +1,20 @@
+[
+ {"id": 1, "name": "Normal"},
+ {"id": 2, "name": "Fighting"},
+ {"id": 3, "name": "Flying"},
+ {"id": 4, "name": "Poison"},
+ {"id": 5, "name": "Ground"},
+ {"id": 6, "name": "Rock"},
+ {"id": 7, "name": "Bug"},
+ {"id": 8, "name": "Ghost"},
+ {"id": 9, "name": "Steel"},
+ {"id": 10, "name": "Fire"},
+ {"id": 11, "name": "Water"},
+ {"id": 12, "name": "Grass"},
+ {"id": 13, "name": "Electric"},
+ {"id": 14, "name": "Psychic"},
+ {"id": 15, "name": "Ice"},
+ {"id": 16, "name": "Dragon"},
+ {"id": 17, "name": "Dark"},
+ {"id": 18, "name": "Fairy"}
+]
diff --git a/examples/dbclient/pokemons/src/main/resources/pokemons.json b/examples/dbclient/pokemons/src/main/resources/pokemons.json
new file mode 100644
index 000000000..c4e78bed7
--- /dev/null
+++ b/examples/dbclient/pokemons/src/main/resources/pokemons.json
@@ -0,0 +1,8 @@
+[
+ {"id": 1, "name": "Bulbasaur", "idType": 12},
+ {"id": 2, "name": "Charmander", "idType": 10},
+ {"id": 3, "name": "Squirtle", "idType": 11},
+ {"id": 4, "name": "Caterpie", "idType": 7},
+ {"id": 5, "name": "Weedle", "idType": 7},
+ {"id": 6, "name": "Pidgey", "idType": 3}
+]
diff --git a/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/AbstractPokemonServiceTest.java b/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/AbstractPokemonServiceTest.java
new file mode 100644
index 000000000..6f51f44de
--- /dev/null
+++ b/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/AbstractPokemonServiceTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2023, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.dbclient.pokemons;
+
+import java.util.List;
+import java.util.Map;
+
+import io.helidon.http.Status;
+import io.helidon.http.media.jsonp.JsonpSupport;
+import io.helidon.webclient.api.ClientResponseTyped;
+import io.helidon.webclient.api.WebClient;
+import io.helidon.webserver.WebServer;
+
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonReader;
+import jakarta.json.JsonValue;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+abstract class AbstractPokemonServiceTest {
+ private static final JsonBuilderFactory JSON_FACTORY = Json.createBuilderFactory(Map.of());
+
+ private static WebServer server;
+ private static WebClient client;
+
+ static void beforeAll() {
+ server = Main.setupServer(WebServer.builder());
+ client = WebClient.create(config -> config.baseUri("http://localhost:" + server.port())
+ .addMediaSupport(JsonpSupport.create()));
+ }
+
+ static void afterAll() {
+ if (server != null && server.isRunning()) {
+ server.stop();
+ }
+ }
+
+ @Test
+ void testListAllPokemons() {
+ ClientResponseTyped response = client.get("/db/pokemon").request(JsonArray.class);
+ assertThat(response.status(), is(Status.OK_200));
+ List names = response.entity().stream().map(AbstractPokemonServiceTest::mapName).toList();
+ assertThat(names, is(pokemonNames()));
+ }
+
+ @Test
+ void testListAllPokemonTypes() {
+ ClientResponseTyped response = client.get("/db/type").request(JsonArray.class);
+ assertThat(response.status(), is(Status.OK_200));
+ List names = response.entity().stream().map(AbstractPokemonServiceTest::mapName).toList();
+ assertThat(names, is(pokemonTypes()));
+ }
+
+ @Test
+ void testGetPokemonById() {
+ ClientResponseTyped response = client.get("/db/pokemon/2").request(JsonObject.class);
+ assertThat(response.status(), is(Status.OK_200));
+ assertThat(name(response.entity()), is("Charmander"));
+ }
+
+ @Test
+ void testGetPokemonByName() {
+ ClientResponseTyped response = client.get("/db/pokemon/name/Squirtle").request(JsonObject.class);
+ assertThat(response.status(), is(Status.OK_200));
+ assertThat(id(response.entity()), is(3));
+ }
+
+ @Test
+ void testAddUpdateDeletePokemon() {
+ JsonObject pokemon;
+ ClientResponseTyped response;
+
+ // add a new Pokémon Rattata
+ pokemon = JSON_FACTORY.createObjectBuilder()
+ .add("id", 7)
+ .add("name", "Rattata")
+ .add("idType", 1)
+ .build();
+ response = client.post("/db/pokemon").submit(pokemon, String.class);
+ assertThat(response.status(), is(Status.CREATED_201));
+
+ // rename Pokémon with id 7 to Raticate
+ pokemon = JSON_FACTORY.createObjectBuilder()
+ .add("id", 7)
+ .add("name", "Raticate")
+ .add("idType", 2)
+ .build();
+
+ response = client.put("/db/pokemon").submit(pokemon, String.class);
+ assertThat(response.status(), is(Status.OK_200));
+
+ // delete Pokémon with id 7
+ response = client.delete("/db/pokemon/7").request(String.class);
+ assertThat(response.status(), is(Status.NO_CONTENT_204));
+
+ response = client.get("/db/pokemon/7").request(String.class);
+ assertThat(response.status(), is(Status.NOT_FOUND_404));
+ }
+
+ private static List pokemonNames() {
+ try (JsonReader reader = Json.createReader(PokemonService.class.getResourceAsStream("/pokemons.json"))) {
+ return reader.readArray().stream().map(AbstractPokemonServiceTest::mapName).toList();
+ }
+ }
+
+ private static List pokemonTypes() {
+ try (JsonReader reader = Json.createReader(PokemonService.class.getResourceAsStream("/pokemon-types.json"))) {
+ return reader.readArray().stream().map(AbstractPokemonServiceTest::mapName).toList();
+ }
+ }
+
+ private static String mapName(JsonValue value) {
+ return name(value.asJsonObject());
+ }
+
+ private static String name(JsonObject json) {
+ return json.containsKey("name")
+ ? json.getString("name")
+ : json.getString("NAME");
+ }
+
+ private static int id(JsonObject json) {
+ return json.containsKey("id")
+ ? json.getInt("id")
+ : json.getInt("ID");
+ }
+
+}
diff --git a/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/PokemonServiceH2IT.java b/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/PokemonServiceH2IT.java
new file mode 100644
index 000000000..25774f174
--- /dev/null
+++ b/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/PokemonServiceH2IT.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2023, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.dbclient.pokemons;
+
+import java.util.Map;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import static io.helidon.config.ConfigSources.classpath;
+
+/**
+ * Tests {@link io.helidon.examples.dbclient.pokemons.PokemonService}.
+ */
+@Testcontainers(disabledWithoutDocker = true)
+class PokemonServiceH2IT extends AbstractPokemonServiceTest {
+ private static final DockerImageName H2_IMAGE = DockerImageName.parse("nemerosa/h2");
+
+ @Container
+ static GenericContainer> container = new GenericContainer<>(H2_IMAGE)
+ .withExposedPorts(9082)
+ .waitingFor(Wait.forLogMessage("(.*)Web Console server running at(.*)", 1));
+
+ @BeforeAll
+ static void start() {
+ String url = String.format("jdbc:h2:tcp://localhost:%s/~./test", container.getMappedPort(9082));
+ Config.global(Config.builder()
+ .addSource(ConfigSources.create(Map.of("db.connection.url", url)))
+ .addSource(classpath("application-h2-test.yaml"))
+ .build());
+ beforeAll();
+ }
+
+ @AfterAll
+ static void stop() {
+ afterAll();
+ }
+}
diff --git a/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/PokemonServiceMongoIT.java b/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/PokemonServiceMongoIT.java
new file mode 100644
index 000000000..d0013d565
--- /dev/null
+++ b/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/PokemonServiceMongoIT.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.dbclient.pokemons;
+
+import java.util.Map;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.testcontainers.containers.MongoDBContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import static io.helidon.config.ConfigSources.classpath;
+
+@Testcontainers(disabledWithoutDocker = true)
+public class PokemonServiceMongoIT extends AbstractPokemonServiceTest {
+
+ @Container
+ static final MongoDBContainer container = new MongoDBContainer("mongo")
+ .withExposedPorts(27017);
+
+ @BeforeAll
+ static void start() {
+ String url = String.format("mongodb://127.0.0.1:%s/pokemon", container.getMappedPort(27017));
+ Config.global(Config.builder()
+ .addSource(ConfigSources.create(Map.of("db.connection.url", url)))
+ .addSource(classpath("application-mongo-test.yaml"))
+ .build());
+ beforeAll();
+ }
+
+ @AfterAll
+ static void stop() {
+ afterAll();
+ }
+
+ void testListAllPokemons() {
+ //Skip this test - Transactions are not supported
+ }
+
+ void testListAllPokemonTypes() {
+ //Skip this test - Transactions are not supported
+ }
+
+ void testGetPokemonById() {
+ //Skip this test - Transactions are not supported
+ }
+
+ void testGetPokemonByName() {
+ //Skip this test - Transactions are not supported
+ }
+
+}
diff --git a/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/PokemonServiceMySQLIT.java b/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/PokemonServiceMySQLIT.java
new file mode 100644
index 000000000..36d03ddc0
--- /dev/null
+++ b/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/PokemonServiceMySQLIT.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2023, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.dbclient.pokemons;
+
+import java.util.Map;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.testcontainers.containers.MySQLContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import static io.helidon.config.ConfigSources.classpath;
+
+@Testcontainers(disabledWithoutDocker = true)
+public class PokemonServiceMySQLIT extends AbstractPokemonServiceTest {
+
+ @Container
+ static MySQLContainer> container = new MySQLContainer<>("mysql:8.0.36")
+ .withUsername("user")
+ .withPassword("changeit")
+ .withNetworkAliases("mysql")
+ .withDatabaseName("pokemon");
+
+ @BeforeAll
+ static void start() {
+ Config.global(Config.builder()
+ .addSource(ConfigSources.create(Map.of("db.connection.url", container.getJdbcUrl())))
+ .addSource(classpath("application-mysql-test.yaml"))
+ .build());
+ beforeAll();
+ }
+
+ @AfterAll
+ static void stop() {
+ afterAll();
+ }
+
+}
diff --git a/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/PokemonServiceOracleIT.java b/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/PokemonServiceOracleIT.java
new file mode 100644
index 000000000..cf2d4f6d9
--- /dev/null
+++ b/examples/dbclient/pokemons/src/test/java/io/helidon/examples/dbclient/pokemons/PokemonServiceOracleIT.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.dbclient.pokemons;
+
+import java.util.Map;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.testcontainers.containers.OracleContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import static io.helidon.config.ConfigSources.classpath;
+
+@Testcontainers(disabledWithoutDocker = true)
+public class PokemonServiceOracleIT extends AbstractPokemonServiceTest {
+
+ private static final DockerImageName image = DockerImageName.parse("wnameless/oracle-xe-11g-r2")
+ .asCompatibleSubstituteFor("gvenzl/oracle-xe");
+
+ @Container
+ static OracleContainer container = new OracleContainer(image)
+ .withExposedPorts(1521, 8080)
+ .withDatabaseName("XE")
+ .usingSid()
+ .waitingFor(Wait.forListeningPorts(1521, 8080));
+
+ @BeforeAll
+ static void setup() {
+ Config.global(Config.builder()
+ .addSource(ConfigSources.create(Map.of("db.connection.url", container.getJdbcUrl())))
+ .addSource(classpath("application-oracle-test.yaml"))
+ .build());
+ beforeAll();
+ }
+
+ @AfterAll
+ static void stop() {
+ afterAll();
+ }
+}
diff --git a/examples/dbclient/pokemons/src/test/resources/application-h2-test.yaml b/examples/dbclient/pokemons/src/test/resources/application-h2-test.yaml
new file mode 100644
index 000000000..daccc3736
--- /dev/null
+++ b/examples/dbclient/pokemons/src/test/resources/application-h2-test.yaml
@@ -0,0 +1,59 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8080
+ host: 0.0.0.0
+
+tracing:
+ service: jdbc-db
+
+# see README.md for details how to run databases in docker
+db:
+ source: jdbc
+ connection:
+ username: sa
+ password: "${EMPTY}"
+ poolName: h2
+ initializationFailTimeout: -1
+ connectionTimeout: 2000
+ helidon:
+ pool-metrics:
+ 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."
+ services:
+ tracing:
+ - enabled: true
+ metrics:
+ - type: TIMER
+ health-check:
+ type: "query"
+ statementName: "health-check"
+ statements:
+ health-check: "SELECT 0"
+ create-types: "CREATE TABLE PokeTypes (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL)"
+ create-pokemons: "CREATE TABLE Pokemons (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL, id_type INTEGER NOT NULL REFERENCES PokeTypes(id))"
+ select-all-types: "SELECT id, name FROM PokeTypes"
+ select-all-pokemons: "SELECT id, name, id_type FROM Pokemons"
+ select-pokemon-by-id: "SELECT id, name, id_type FROM Pokemons WHERE id = :id"
+ select-pokemon-by-name: "SELECT id, name, id_type FROM Pokemons WHERE name = ?"
+ insert-type: "INSERT INTO PokeTypes(id, name) VALUES(?, ?)"
+ insert-pokemon: "INSERT INTO Pokemons(id, name, id_type) VALUES(?, ?, ?)"
+ update-pokemon-by-id: "UPDATE Pokemons SET name = :name, id_type = :idType WHERE id = :id"
+ delete-pokemon-by-id: "DELETE FROM Pokemons WHERE id = :id"
+ delete-all-types: "DELETE FROM PokeTypes"
+ delete-all-pokemons: "DELETE FROM Pokemons"
diff --git a/examples/dbclient/pokemons/src/test/resources/application-mongo-test.yaml b/examples/dbclient/pokemons/src/test/resources/application-mongo-test.yaml
new file mode 100644
index 000000000..aee39d5e4
--- /dev/null
+++ b/examples/dbclient/pokemons/src/test/resources/application-mongo-test.yaml
@@ -0,0 +1,113 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8080
+ host: 0.0.0.0
+
+tracing:
+ service: mongo-db
+
+db:
+ source: "mongoDb"
+ init-schema: false
+ # Transactions are not supported
+ init-data: false
+ services:
+ tracing:
+ - enabled: true
+ health-check:
+ type: "query"
+ statementName: "health-check"
+ statements:
+ # Health check statement. HealthCheck statement type must be a query.
+ health-check: '{
+ "operation": "command",
+ "query": { ping: 1 }
+ }'
+ ## Create database schema
+ # Select all types
+ select-all-types: '{
+ "collection": "types",
+ "operation": "query",
+ "projection": { id: 1, name: 1, _id: 0 },
+ "query": {}
+ }'
+ # Select all pokemons without type information
+ select-all-pokemons: '{
+ "collection": "pokemons",
+ "operation": "query",
+ "projection": { id: 1, name: 1, id_type: 1, _id: 0 },
+ "query": {}
+ }'
+ # Select pokemon by id
+ select-pokemon-by-id: '{
+ "collection": "pokemons",
+ "operation": "query",
+ "projection": { id: 1, name: 1, id_type: 1, _id: 0 },
+ "query": { id: $id }
+ }'
+ # Select pokemon by name
+ select-pokemon-by-name: '{
+ "collection": "pokemons",
+ "operation": "query",
+ "projection": { id: 1, name: 1, id_type: 1, _id: 0 },
+ "query": { name: ? }
+ }'
+ # Insert records into database
+ insert-type: '{
+ "collection": "types",
+ "operation": "insert",
+ "value": {
+ "id": ?,
+ "name": ?
+ }
+ }'
+ insert-pokemon: '{
+ "collection": "pokemons",
+ "operation": "insert",
+ "value": {
+ "id": ?,
+ "name": ?,
+ "id_type": ?
+ }
+ }'
+ # Update name of pokemon specified by id
+ update-pokemon-by-id: '{
+ "collection": "pokemons",
+ "operation": "update",
+ "value":{ $set: { "name": $name, "id_type": $idType } },
+ "query": { id: $id }
+ }'
+ # Delete pokemon by id
+ delete-pokemon-by-id: '{
+ "collection": "pokemons",
+ "operation": "delete",
+ "query": { id: $id }
+ }'
+ # Delete all types
+ delete-all-types: '{
+ "collection": "types",
+ "operation": "delete",
+ "query": { }
+ }'
+ # Delete all pokemons
+ delete-all-pokemons: '{
+ "collection": "pokemons",
+ "operation": "delete",
+ "query": { }
+ }'
+
diff --git a/examples/dbclient/pokemons/src/test/resources/application-mysql-test.yaml b/examples/dbclient/pokemons/src/test/resources/application-mysql-test.yaml
new file mode 100644
index 000000000..58e56aee3
--- /dev/null
+++ b/examples/dbclient/pokemons/src/test/resources/application-mysql-test.yaml
@@ -0,0 +1,59 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8080
+ host: 0.0.0.0
+
+tracing:
+ service: jdbc-db
+
+# see README.md for details how to run databases in docker
+db:
+ source: jdbc
+ connection:
+ username: user
+ password: changeit
+ poolName: "mysql"
+ initializationFailTimeout: -1
+ connectionTimeout: 2000
+ helidon:
+ pool-metrics:
+ 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."
+ services:
+ tracing:
+ - enabled: true
+ metrics:
+ - type: TIMER
+ health-check:
+ type: "query"
+ statementName: "health-check"
+ statements:
+ health-check: "SELECT 0"
+ create-types: "CREATE TABLE PokeTypes (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL)"
+ create-pokemons: "CREATE TABLE Pokemons (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL, id_type INTEGER NOT NULL REFERENCES PokeTypes(id))"
+ select-all-types: "SELECT id, name FROM PokeTypes"
+ select-all-pokemons: "SELECT id, name, id_type FROM Pokemons"
+ select-pokemon-by-id: "SELECT id, name, id_type FROM Pokemons WHERE id = :id"
+ select-pokemon-by-name: "SELECT id, name, id_type FROM Pokemons WHERE name = ?"
+ insert-type: "INSERT INTO PokeTypes(id, name) VALUES(?, ?)"
+ insert-pokemon: "INSERT INTO Pokemons(id, name, id_type) VALUES(?, ?, ?)"
+ update-pokemon-by-id: "UPDATE Pokemons SET name = :name, id_type = :idType WHERE id = :id"
+ delete-pokemon-by-id: "DELETE FROM Pokemons WHERE id = :id"
+ delete-all-types: "DELETE FROM PokeTypes"
+ delete-all-pokemons: "DELETE FROM Pokemons"
diff --git a/examples/dbclient/pokemons/src/test/resources/application-oracle-test.yaml b/examples/dbclient/pokemons/src/test/resources/application-oracle-test.yaml
new file mode 100644
index 000000000..66d62ec06
--- /dev/null
+++ b/examples/dbclient/pokemons/src/test/resources/application-oracle-test.yaml
@@ -0,0 +1,58 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8080
+ host: 0.0.0.0
+
+tracing:
+ service: jdbc-db
+
+# see README.md for details how to run databases in docker
+db:
+ source: jdbc
+ connection:
+ username: "system"
+ password: "oracle"
+ initializationFailTimeout: -1
+ connectionTimeout: 2000
+ helidon:
+ pool-metrics:
+ 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."
+ services:
+ tracing:
+ - enabled: true
+ metrics:
+ - type: TIMER
+ health-check:
+ type: "query"
+ statementName: "health-check"
+ statements:
+ health-check: "SELECT 1 FROM DUAL"
+ create-types: "CREATE TABLE PokeTypes (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL)"
+ create-pokemons: "CREATE TABLE Pokemons (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL, id_type INTEGER NOT NULL REFERENCES PokeTypes(id))"
+ select-all-types: "SELECT id, name FROM PokeTypes"
+ select-all-pokemons: "SELECT id, name, id_type FROM Pokemons"
+ select-pokemon-by-id: "SELECT id, name, id_type FROM Pokemons WHERE id = :id"
+ select-pokemon-by-name: "SELECT id, name, id_type FROM Pokemons WHERE name = ?"
+ insert-type: "INSERT INTO PokeTypes(id, name) VALUES(?, ?)"
+ insert-pokemon: "INSERT INTO Pokemons(id, name, id_type) VALUES(?, ?, ?)"
+ update-pokemon-by-id: "UPDATE Pokemons SET name = :name, id_type = :idType WHERE id = :id"
+ delete-pokemon-by-id: "DELETE FROM Pokemons WHERE id = :id"
+ delete-all-types: "DELETE FROM PokeTypes"
+ delete-all-pokemons: "DELETE FROM Pokemons"
diff --git a/examples/dbclient/pom.xml b/examples/dbclient/pom.xml
new file mode 100644
index 000000000..34c3f28ee
--- /dev/null
+++ b/examples/dbclient/pom.xml
@@ -0,0 +1,46 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples
+ helidon-examples-project
+ 1.0.0-SNAPSHOT
+
+
+ pom
+
+ io.helidon.examples.dbclient
+ helidon-examples-dbclient-project
+ 1.0.0-SNAPSHOT
+ Helidon Examples DB Client
+
+
+ Examples of Helidon DB Client
+
+
+
+
+ jdbc
+ mongodb
+ common
+ pokemons
+
+
diff --git a/examples/employee-app/.dockerignore b/examples/employee-app/.dockerignore
new file mode 100644
index 000000000..c8b241f22
--- /dev/null
+++ b/examples/employee-app/.dockerignore
@@ -0,0 +1 @@
+target/*
\ No newline at end of file
diff --git a/examples/employee-app/Dockerfile b/examples/employee-app/Dockerfile
new file mode 100644
index 000000000..fc8d64243
--- /dev/null
+++ b/examples/employee-app/Dockerfile
@@ -0,0 +1,54 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# 1st stage, build the app
+FROM container-registry.oracle.com/java/jdk-no-fee-term:21 as build
+
+# Install maven
+WORKDIR /usr/share
+RUN set -x && \
+ curl -O https://archive.apache.org/dist/maven/maven-3/3.8.4/binaries/apache-maven-3.8.4-bin.tar.gz && \
+ tar -xvf apache-maven-*-bin.tar.gz && \
+ rm apache-maven-*-bin.tar.gz && \
+ mv apache-maven-* maven && \
+ ln -s /usr/share/maven/bin/mvn /bin/
+
+WORKDIR /helidon
+
+# Create a first layer to cache the "Maven World" in the local repository.
+# Incremental docker builds will always resume after that, unless you update
+# the pom
+ADD pom.xml .
+RUN mvn package -Dmaven.test.skip
+
+# Do the Maven build!
+# Incremental docker builds will resume here when you change sources
+ADD src src
+RUN mvn package -DskipTests
+
+RUN echo "done!"
+
+# 2nd stage, build the runtime image
+FROM container-registry.oracle.com/java/jdk-no-fee-term:21
+WORKDIR /helidon
+
+# Copy the binary built in the 1st stage
+COPY --from=build /helidon/target/helidon-examples-employee-app.jar ./
+COPY --from=build /helidon/target/libs ./libs
+
+CMD ["java", "-jar", "helidon-examples-employee-app.jar"]
+
+EXPOSE 8080
diff --git a/examples/employee-app/README.md b/examples/employee-app/README.md
new file mode 100644
index 000000000..9a1779c45
--- /dev/null
+++ b/examples/employee-app/README.md
@@ -0,0 +1,293 @@
+# Helidon Quickstart SE - Employee Directory Example
+
+This project implements an employee directory REST service using Helidon SE.
+ The application is composed of a Helidon REST Microservice backend along with
+ an HTML/JavaScript front end. The source for both application is included with
+ the Maven project.
+
+By default, the service uses a ArrayList backend with sample data. You can connect
+ the backend application to an Oracle database by changing the values in the
+ `resources/application.yaml` file.
+
+The service uses Helidon DB Client to access the database.
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-employee-app.jar
+```
+
+## Create script
+If you do not have the employee table in your database, you can create it and required resources as follows:
+
+```sql
+CREATE TABLE EMPLOYEE (ID NUMBER(6),
+ FIRSTNAME VARCHAR2(20),
+ LASTNAME VARCHAR2(25),
+ EMAIL VARCHAR2(30),
+ PHONE VARCHAR2(30),
+ BIRTHDATE VARCHAR2(15),
+ TITLE VARCHAR2(20),
+ DEPARTMENT VARCHAR2(20));
+
+ALTER TABLE EMPLOYEE ADD (CONSTRAINT emp_id_pk PRIMARY KEY (ID));
+
+CREATE SEQUENCE EMPLOYEE_SEQ INCREMENT BY 1 NOCACHE NOCYCLE;
+```
+
+## Exercise the application
+Get all employees.
+```shell
+curl -X GET curl -X GET http://localhost:8080/employees
+```
+
+Only 1 output record is shown for brevity:
+```json
+[
+ {
+ "birthDate": "1970-11-28T08:28:48.078Z",
+ "department": "Mobility",
+ "email": "Hugh.Jast@example.com",
+ "firstName": "Hugh",
+ "id": "48cf06ad-6ed4-47e6-ac44-3ea9c67cbe2d",
+ "lastName": "Jast",
+ "phone": "730-715-4446",
+ "title": "National Data Strategist"
+ }
+]
+```
+
+
+Get all employees whose last name contains "S".
+```shell
+curl -X GET http://localhost:8080/employees/lastname/S
+```
+
+Only 1 output record is shown for brevity:
+```json
+[
+ {
+ "birthDate": "1978-03-18T17:00:12.938Z",
+ "department": "Security",
+ "email": "Zora.Sawayn@example.com",
+ "firstName": "Zora",
+ "id": "d7b583a2-f068-40d9-aec0-6f87899c5d8a",
+ "lastName": "Sawayn",
+ "phone": "923-814-0502",
+ "title": "Dynamic Marketing Designer"
+ }
+]
+```
+
+Get an individual record.
+```shell
+curl -X GET http://localhost:8080/employees/48cf06ad-6ed4-47e6-ac44-3ea9c67cbe2d
+```
+Output:
+```json
+[
+ {
+ "birthDate": "1970-11-28T08:28:48.078Z",
+ "department": "Mobility",
+ "email": "Hugh.Jast@example.com",
+ "firstName": "Hugh",
+ "id": "48cf06ad-6ed4-47e6-ac44-3ea9c67cbe2d",
+ "lastName": "Jast",
+ "phone": "730-715-4446",
+ "title": "National Data Strategist"
+ }
+]
+```
+
+Connect with a web browser at:
+```txt
+http://localhost:8080/public/index.html
+```
+
+
+## Try health and metrics
+
+```shell
+curl -s -X GET http://localhost:8080/health
+```
+
+```json
+{
+ "outcome": "UP",
+ "checks": [
+ {
+ "name": "deadlock",
+ "state": "UP"
+ },
+ {
+ "name": "diskSpace",
+ "state": "UP",
+ "data": {
+ "free": "306.61 GB",
+ "freeBytes": 329225338880,
+ "percentFree": "65.84%",
+ "total": "465.72 GB",
+ "totalBytes": 500068036608
+ }
+ },
+ {
+ "name": "heapMemory",
+ "state": "UP",
+ "data": {
+ "free": "239.35 MB",
+ "freeBytes": 250980656,
+ "max": "4.00 GB",
+ "maxBytes": 4294967296,
+ "percentFree": "99.59%",
+ "total": "256.00 MB",
+ "totalBytes": 268435456
+ }
+ }
+ ]
+}
+```
+
+### Prometheus Format
+
+```shell
+curl -s -X GET http://localhost:8080/metrics
+```
+
+Only 1 output item is shown for brevity:
+```txt
+# TYPE base:classloader_current_loaded_class_count counter
+# HELP base:classloader_current_loaded_class_count Displays the number of classes that are currently loaded in the Java virtual machine.
+base:classloader_current_loaded_class_count 3995
+```
+
+### JSON Format
+```shell
+curl -H 'Accept: application/json' -X GET http://localhost:8080/metrics
+```
+
+Output:
+```json
+{
+ "base": {
+ "classloader.currentLoadedClass.count": 4011,
+ "classloader.totalLoadedClass.count": 4011,
+ "classloader.totalUnloadedClass.count": 0,
+ "cpu.availableProcessors": 8,
+ "cpu.systemLoadAverage": 1.65283203125,
+ "gc.G1 Old Generation.count": 0,
+ "gc.G1 Old Generation.time": 0,
+ "gc.G1 Young Generation.count": 2,
+ "gc.G1 Young Generation.time": 8,
+ "jvm.uptime": 478733,
+ "memory.committedHeap": 268435456,
+ "memory.maxHeap": 4294967296,
+ "memory.usedHeap": 18874368,
+ "thread.count": 11,
+ "thread.daemon.count": 4,
+ "thread.max.count": 11
+ },
+ "vendor": {
+ "grpc.requests.count": 0,
+ "grpc.requests.meter": {
+ "count": 0,
+ "meanRate": 0,
+ "oneMinRate": 0,
+ "fiveMinRate": 0,
+ "fifteenMinRate": 0
+ },
+ "requests.count": 5,
+ "requests.meter": {
+ "count": 5,
+ "meanRate": 0.01046407983617782,
+ "oneMinRate": 0.0023897243038835964,
+ "fiveMinRate": 0.003944597070306631,
+ "fifteenMinRate": 0.0023808575122958794
+ }
+ }
+}
+```
+
+## Build the Docker Image
+
+```shell
+docker build -t employee-app .
+```
+
+## Start the application with Docker
+
+```shell
+docker run --rm -p 8080:8080 employee-app:latest
+```
+
+Exercise the application as described above.
+
+## Deploy the application to Kubernetes
+
+```txt
+kubectl cluster-info # Verify which cluster
+kubectl get pods # Verify connectivity to cluster
+kubectl create -f app.yaml # Deply application
+kubectl get service employee-app # Get service info
+```
+
+
+### Oracle DB Credentials
+You can connect to two different datastores for the back end application.
+ Just fill in the application.yaml files. To use an ArrayList as the data store,
+ simply set `drivertype` to `Array`. To connect to an Oracle database, you must
+ set all the values: `user`, `password`, `hosturl`, and `drivertype`.
+ For Oracle, the `drivertype` should be set to `Oracle`.
+
+**Sample `application.yaml`**
+```yaml
+app:
+ user:
+ password:
+ hosturl: :/.
+ drivertype: Array
+
+ server:
+ port: 8080
+ host: 0.0.0.0
+```
+
+## Create the database objects
+
+1. Create a connection to your Oracle Database using sqlplus or SQL Developer.
+ See https://docs.cloud.oracle.com/iaas/Content/Database/Tasks/connectingDB.htm.
+2. Create the database objects:
+
+```sql
+CREATE TABLE EMPLOYEE (
+ ID INTEGER NOT NULL,
+ FIRSTNAME VARCHAR(100),
+ LASTNAME VARCHAR(100),
+ EMAIL VARCHAR(100),
+ PHONE VARCHAR(100),
+ BIRTHDATE VARCHAR(10),
+ TITLE VARCHAR(100),
+ DEPARTMENT VARCHAR(100),
+ PRIMARY KEY (ID)
+ );
+```
+
+```sql
+CREATE SEQUENCE EMPLOYEE_SEQ
+ START WITH 100
+ INCREMENT BY 1;
+```
+
+```sql
+INSERT INTO EMPLOYEE (ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT) VALUES (EMPLOYEE_SEQ.nextVal, 'Hugh', 'Jast', 'Hugh.Jast@example.com', '730-555-0100', '1970-11-28', 'National Data Strategist', 'Mobility');
+
+INSERT INTO EMPLOYEE (ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT) VALUES (EMPLOYEE_SEQ.nextVal, 'Toy', 'Herzog', 'Toy.Herzog@example.com', '769-555-0102', '1961-08-08', 'Dynamic Operations Manager', 'Paradigm');
+
+INSERT INTO EMPLOYEE (ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT) VALUES (EMPLOYEE_SEQ.nextVal, 'Reed', 'Hahn', 'Reed.Hahn@example.com', '429-555-0153', '1977-02-05', 'Future Directives Facilitator', 'Quality');
+
+INSERT INTO EMPLOYEE (ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT) VALUES (EMPLOYEE_SEQ.nextVal, 'Novella', 'Bahringer', 'Novella.Bahringer@example.com', '293-596-3547', '1961-07-25', 'Principal Factors Architect', 'Division');
+
+INSERT INTO EMPLOYEE (ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT) VALUES (EMPLOYEE_SEQ.nextVal, 'Zora', 'Sawayn', 'Zora.Sawayn@example.com', '923-555-0161', '1978-03-18', 'Dynamic Marketing Designer', 'Security');
+
+INSERT INTO EMPLOYEE (ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT) VALUES (EMPLOYEE_SEQ.nextVal, 'Cordia', 'Willms', 'Cordia.Willms@example.com', '778-555-0187', '1989-03-31', 'Human Division Representative', 'Optimization');
+```
\ No newline at end of file
diff --git a/examples/employee-app/app.yaml b/examples/employee-app/app.yaml
new file mode 100644
index 000000000..7f3d18e65
--- /dev/null
+++ b/examples/employee-app/app.yaml
@@ -0,0 +1,63 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+kind: Service
+apiVersion: v1
+metadata:
+ name: helidon-examples-employee-app
+ labels:
+ app: helidon-examples-employee-app
+spec:
+ type: NodePort
+ selector:
+ app: helidon-examples-employee-app
+ ports:
+ - port: 8080
+ targetPort: 8080
+ name: http
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: helidon-examples-employee-app
+spec:
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ app: helidon-examples-employee-app
+ version: v1
+ spec:
+ containers:
+ - name: helidon-examples-employee-app
+ image: helidon-examples-employee-app
+ imagePullPolicy: IfNotPresent
+ ports:
+ - containerPort: 8080
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: helidon-examples-employee-app
+ labels:
+ app: helidon-examples-employee-app
+spec:
+ type: LoadBalancer
+ ports:
+ - port: 80
+ targetPort: 8080
+ selector:
+ app: helidon-examples-employee-app
\ No newline at end of file
diff --git a/examples/employee-app/pom.xml b/examples/employee-app/pom.xml
new file mode 100644
index 000000000..d72b905ec
--- /dev/null
+++ b/examples/employee-app/pom.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.employee
+ helidon-examples-employee-app
+ 1.0.0-SNAPSHOT
+ Helidon Examples Employee App
+
+
+ io.helidon.examples.employee.Main
+
+
+
+
+ io.helidon.integrations.db
+ ojdbc
+
+
+ com.oracle.database.jdbc
+ ucp
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe
+
+
+ io.helidon.webserver
+ helidon-webserver-static-content
+
+
+ io.helidon.http.media
+ helidon-http-media-jsonb
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.health
+ helidon-health-checks
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-metrics
+ runtime
+
+
+ io.helidon.metrics
+ helidon-metrics-system-meters
+ runtime
+
+
+ io.helidon.dbclient
+ helidon-dbclient
+
+
+ io.helidon.dbclient
+ helidon-dbclient-jdbc
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
+
diff --git a/examples/employee-app/src/main/java/io/helidon/examples/employee/Employee.java b/examples/employee-app/src/main/java/io/helidon/examples/employee/Employee.java
new file mode 100644
index 000000000..b15b9d5af
--- /dev/null
+++ b/examples/employee-app/src/main/java/io/helidon/examples/employee/Employee.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.employee;
+
+import java.util.UUID;
+
+import jakarta.json.bind.annotation.JsonbCreator;
+import jakarta.json.bind.annotation.JsonbProperty;
+/**
+ * Represents an employee.
+ */
+public final class Employee {
+
+ private final String id;
+ private final String firstName;
+ private final String lastName;
+ private final String email;
+ private final String phone;
+ private final String birthDate;
+ private final String title;
+ private final String department;
+
+ /**Creates a new Employee.
+ * @param id The employee ID.
+ * @param firstName The employee first name.
+ * @param lastName The employee lastName.
+ * @param email The employee email.
+ * @param phone The employee phone.
+ * @param birthDate The employee birthDatee.
+ * @param title The employee title.
+ * @param department The employee department.*/
+ @SuppressWarnings("checkstyle:ParameterNumber")
+ private Employee(String id, String firstName, String lastName, String email, String phone, String birthDate,
+ String title, String department) {
+ this.id = id;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.email = email;
+ this.phone = phone;
+ this.birthDate = birthDate;
+ this.title = title;
+ this.department = department;
+ }
+
+ /**
+ * Creates a new employee. This method helps to parse the json parameters in the requests.
+ * @param id The employee ID. If the employee ID is null or empty generates a new ID.
+ * @param firstName The employee first name.
+ * @param lastName The employee lastName.
+ * @param email The employee email.
+ * @param phone The employee phone.
+ * @param birthDate The employee birthDatee.
+ * @param title The employee title.
+ * @param department The employee department.
+ * @return A new employee object
+ */
+ @JsonbCreator
+ @SuppressWarnings("checkstyle:ParameterNumber")
+ public static Employee of(@JsonbProperty("id") String id, @JsonbProperty("firstName") String firstName,
+ @JsonbProperty("lastName") String lastName, @JsonbProperty("email") String email,
+ @JsonbProperty("phone") String phone, @JsonbProperty("birthDate") String birthDate,
+ @JsonbProperty("title") String title, @JsonbProperty("department") String department) {
+ if (id == null || id.trim().equals("")) {
+ id = UUID.randomUUID().toString();
+ }
+ Employee e = new Employee(id, firstName, lastName, email, phone, birthDate, title, department);
+ return e;
+ }
+
+ /**
+ * Returns the employee ID.
+ * @return the ID
+ */
+ public String getId() {
+ return this.id;
+ }
+
+ /**
+ * Returns the employee first name.
+ * @return The first name
+ */
+ public String getFirstName() {
+ return this.firstName;
+ }
+
+ /**
+ * Returns the employee last name.
+ * @return The last name
+ */
+ public String getLastName() {
+ return this.lastName;
+ }
+
+ /**
+ * Returns the employee e-mail.
+ * @return The email
+ */
+ public String getEmail() {
+ return this.email;
+ }
+
+ /**
+ * Returns the employee phone.
+ * @return The phone
+ */
+ public String getPhone() {
+ return this.phone;
+ }
+
+ /**
+ * Returns the employee birthdate.
+ * @return The birthdate
+ */
+ public String getBirthDate() {
+ return this.birthDate;
+ }
+
+ /**
+ * Returns the employee title.
+ * @return The title
+ */
+ public String getTitle() {
+ return this.title;
+ }
+
+ /**
+ * Returns the employee department.
+ * @return The department
+ */
+ public String getDepartment() {
+ return this.department;
+ }
+
+ @Override
+ public String toString() {
+ return "ID: " + id + " First Name: " + firstName + " Last Name: " + lastName + " EMail: " + email + " Phone: "
+ + phone + " Birth Date: " + birthDate + " Title: " + title + " Department: " + department;
+ }
+
+}
diff --git a/examples/employee-app/src/main/java/io/helidon/examples/employee/EmployeeRepository.java b/examples/employee-app/src/main/java/io/helidon/examples/employee/EmployeeRepository.java
new file mode 100644
index 000000000..1ce65c59f
--- /dev/null
+++ b/examples/employee-app/src/main/java/io/helidon/examples/employee/EmployeeRepository.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.employee;
+
+import java.util.List;
+import java.util.Optional;
+
+import io.helidon.config.Config;
+
+/**
+ * Interface for Data Access Objects.
+ */
+public interface EmployeeRepository {
+
+ /**
+ * Create a new employeeRepository instance using one of the two implementations
+ * {@link EmployeeRepositoryImpl} or {@link EmployeeRepositoryImplDB} depending
+ * on the specified driver type.
+ *
+ * @param driverType Represents the driver type. It can be Array or Oracle.
+ * @param config Contains the application configuration specified in the
+ * application.yaml
file.
+ * @return The employee repository implementation.
+ */
+ static EmployeeRepository create(String driverType, Config config) {
+ return switch (driverType) {
+ case "Database" -> new EmployeeRepositoryImplDB(config);
+ default ->
+ // Array is default
+ new EmployeeRepositoryImpl();
+ };
+ }
+
+ /**
+ * Returns the list of the employees.
+ *
+ * @return The collection of all the employee objects
+ */
+ List getAll();
+
+ /**
+ * Returns the list of the employees that match with the specified lastName.
+ *
+ * @param lastName Represents the last name value for the search.
+ * @return The collection of the employee objects that match with the specified
+ * lastName
+ */
+ List getByLastName(String lastName);
+
+ /**
+ * Returns the list of the employees that match with the specified title.
+ *
+ * @param title Represents the title value for the search
+ * @return The collection of the employee objects that match with the specified
+ * title
+ */
+ List getByTitle(String title);
+
+ /**
+ * Returns the list of the employees that match with the specified department.
+ *
+ * @param department Represents the department value for the search.
+ * @return The collection of the employee objects that match with the specified
+ * department
+ */
+ List getByDepartment(String department);
+
+ /**
+ * Add a new employee.
+ *
+ * @param employee returns the employee object including the ID generated.
+ * @return the employee object including the ID generated
+ */
+ Employee save(Employee employee); // Add new employee
+
+ /**
+ * Update an existing employee.
+ *
+ * @param updatedEmployee The employee object with the values to update
+ * @param id The employee ID
+ * @return number of updated records
+ */
+ long update(Employee updatedEmployee, String id);
+
+ /**
+ * Delete an employee by ID.
+ *
+ * @param id The employee ID
+ * @return number of deleted records
+ */
+ long deleteById(String id);
+
+ /**
+ * Get an employee by ID.
+ *
+ * @param id The employee ID
+ * @return The employee object if the employee is found
+ */
+ Optional getById(String id);
+}
diff --git a/examples/employee-app/src/main/java/io/helidon/examples/employee/EmployeeRepositoryImpl.java b/examples/employee-app/src/main/java/io/helidon/examples/employee/EmployeeRepositoryImpl.java
new file mode 100644
index 000000000..2a9498025
--- /dev/null
+++ b/examples/employee-app/src/main/java/io/helidon/examples/employee/EmployeeRepositoryImpl.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.employee;
+
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import jakarta.json.bind.Jsonb;
+import jakarta.json.bind.JsonbBuilder;
+import jakarta.json.bind.JsonbConfig;
+
+/**
+ * Implementation of the {@link EmployeeRepository}. This implementation uses a
+ * mock database written with in-memory ArrayList classes.
+ * The strings id, name, and other search strings are validated before being
+ * passed to the methods in this class.
+ */
+public final class EmployeeRepositoryImpl implements EmployeeRepository {
+
+ private final CopyOnWriteArrayList eList = new CopyOnWriteArrayList<>();
+
+ /**
+ * To load the initial data, parses the content of employee.json
+ * file located in the resources
directory to a list of Employee
+ * objects.
+ */
+ public EmployeeRepositoryImpl() {
+ JsonbConfig config = new JsonbConfig().withFormatting(Boolean.TRUE);
+ try (Jsonb jsonb = JsonbBuilder.create(config);
+ InputStream jsonFile = EmployeeRepositoryImpl.class.getResourceAsStream("/employees.json")) {
+ Employee[] employees = jsonb.fromJson(jsonFile, Employee[].class);
+ eList.addAll(Arrays.asList(employees));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public List getByLastName(String name) {
+ return eList.stream()
+ .filter((e) -> (e.getLastName().contains(name)))
+ .toList();
+ }
+
+ @Override
+ public List getByTitle(String title) {
+ return eList.stream()
+ .filter((e) -> (e.getTitle().contains(title)))
+ .toList();
+ }
+
+ @Override
+ public List getByDepartment(String department) {
+ return eList.stream()
+ .filter((e) -> (e.getDepartment().contains(department)))
+ .toList();
+ }
+
+ @Override
+ public List getAll() {
+ return eList;
+ }
+
+ @Override
+ public Optional getById(String id) {
+ return eList.stream().filter(e -> e.getId().equals(id)).findFirst();
+ }
+
+ @Override
+ public Employee save(Employee employee) {
+ Employee nextEmployee = Employee.of(null,
+ employee.getFirstName(),
+ employee.getLastName(),
+ employee.getEmail(),
+ employee.getPhone(),
+ employee.getBirthDate(),
+ employee.getTitle(),
+ employee.getDepartment());
+ eList.add(nextEmployee);
+ return nextEmployee;
+ }
+
+ @Override
+ public long update(Employee updatedEmployee, String id) {
+ deleteById(id);
+ Employee e = Employee.of(id, updatedEmployee.getFirstName(), updatedEmployee.getLastName(),
+ updatedEmployee.getEmail(), updatedEmployee.getPhone(), updatedEmployee.getBirthDate(),
+ updatedEmployee.getTitle(), updatedEmployee.getDepartment());
+ eList.add(e);
+ return 1L;
+ }
+
+ @Override
+ public long deleteById(String id) {
+ return eList.stream()
+ .filter(e -> e.getId().equals(id))
+ .findFirst()
+ .map(eList::remove)
+ .map(it -> 1L)
+ .orElse(0L);
+ }
+}
diff --git a/examples/employee-app/src/main/java/io/helidon/examples/employee/EmployeeRepositoryImplDB.java b/examples/employee-app/src/main/java/io/helidon/examples/employee/EmployeeRepositoryImplDB.java
new file mode 100644
index 000000000..e95694c0b
--- /dev/null
+++ b/examples/employee-app/src/main/java/io/helidon/examples/employee/EmployeeRepositoryImplDB.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.employee;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import io.helidon.config.Config;
+import io.helidon.dbclient.DbClient;
+import io.helidon.dbclient.DbRow;
+import io.helidon.dbclient.jdbc.JdbcClientBuilder;
+
+/**
+ * Implementation of the {@link EmployeeRepository}. This implementation uses an
+ * Oracle database to persist the Employee objects.
+ */
+final class EmployeeRepositoryImplDB implements EmployeeRepository {
+
+ private final DbClient dbClient;
+
+ /**
+ * Creates the database connection using the parameters specified in the
+ * application.yaml
file located in the resources
directory.
+ *
+ * @param config Represents the application configuration.
+ */
+ EmployeeRepositoryImplDB(Config config) {
+ String url = "jdbc:oracle:thin:@";
+ String driver = "oracle.jdbc.driver.OracleDriver";
+
+ String dbUserName = config.get("app.user").asString().orElse("sys as SYSDBA");
+ String dbUserPassword = config.get("app.password").asString().orElse("changeit");
+ String dbHostURL = config.get("app.hosturl").asString().orElse("localhost:1521/xe");
+
+ try {
+ Class.forName(driver);
+ } catch (Exception sqle) {
+ sqle.printStackTrace();
+ }
+
+ // now we create the DB Client - explicitly use JDBC, so we can
+ // configure JDBC specific configuration
+ dbClient = JdbcClientBuilder.create()
+ .url(url + dbHostURL)
+ .username(dbUserName)
+ .password(dbUserPassword)
+ .build();
+ }
+
+ @Override
+ public List getAll() {
+ String queryStr = "SELECT * FROM EMPLOYEE";
+
+ return toEmployeeList(dbClient.execute().query(queryStr));
+ }
+
+ @Override
+ public List getByLastName(String name) {
+ String queryStr = "SELECT * FROM EMPLOYEE WHERE LASTNAME LIKE ?";
+
+ return toEmployeeList(dbClient.execute().query(queryStr, name));
+ }
+
+ @Override
+ public List getByTitle(String title) {
+ String queryStr = "SELECT * FROM EMPLOYEE WHERE TITLE LIKE ?";
+
+ return toEmployeeList(dbClient.execute().query(queryStr, title));
+ }
+
+ @Override
+ public List getByDepartment(String department) {
+ String queryStr = "SELECT * FROM EMPLOYEE WHERE DEPARTMENT LIKE ?";
+
+ return toEmployeeList(dbClient.execute().query(queryStr, department));
+ }
+
+ @Override
+ public Employee save(Employee employee) {
+ String insertTableSQL = "INSERT INTO EMPLOYEE "
+ + "(ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT) "
+ + "VALUES(EMPLOYEE_SEQ.NEXTVAL,?,?,?,?,?,?,?)";
+
+ dbClient.execute()
+ .createInsert(insertTableSQL)
+ .addParam(employee.getFirstName())
+ .addParam(employee.getLastName())
+ .addParam(employee.getEmail())
+ .addParam(employee.getPhone())
+ .addParam(employee.getBirthDate())
+ .addParam(employee.getTitle())
+ .addParam(employee.getDepartment())
+ .execute();
+ // let's always return the employee once the insert finishes
+ return employee;
+ }
+
+ @Override
+ public long deleteById(String id) {
+ String deleteRowSQL = "DELETE FROM EMPLOYEE WHERE ID=?";
+
+ return dbClient.execute().delete(deleteRowSQL, id);
+ }
+
+ @Override
+ public Optional getById(String id) {
+ String queryStr = "SELECT * FROM EMPLOYEE WHERE ID =?";
+
+ return dbClient.execute()
+ .get(queryStr, id)
+ .map(row -> row.as(Employee.class));
+ }
+
+ @Override
+ public long update(Employee updatedEmployee, String id) {
+ String updateTableSQL = "UPDATE EMPLOYEE SET FIRSTNAME=?, LASTNAME=?, EMAIL=?, PHONE=?, BIRTHDATE=?, TITLE=?, "
+ + "DEPARTMENT=? WHERE ID=?";
+
+ return dbClient.execute()
+ .createUpdate(updateTableSQL)
+ .addParam(updatedEmployee.getFirstName())
+ .addParam(updatedEmployee.getLastName())
+ .addParam(updatedEmployee.getEmail())
+ .addParam(updatedEmployee.getPhone())
+ .addParam(updatedEmployee.getBirthDate())
+ .addParam(updatedEmployee.getTitle())
+ .addParam(updatedEmployee.getDepartment())
+ .addParam(Integer.parseInt(id))
+ .execute();
+ }
+
+ private static List toEmployeeList(Stream rows) {
+ return rows.map(EmployeeDbMapper::read).toList();
+ }
+
+ private static final class EmployeeDbMapper {
+ private EmployeeDbMapper() {
+ }
+
+ static Employee read(DbRow row) {
+ // map named columns to an object
+ return Employee.of(
+ row.column("ID").get(String.class),
+ row.column("FIRSTNAME").get(String.class),
+ row.column("LASTNAME").get(String.class),
+ row.column("EMAIL").get(String.class),
+ row.column("PHONE").get(String.class),
+ row.column("BIRTHDATE").get(String.class),
+ row.column("TITLE").get(String.class),
+ row.column("DEPARTMENT").get(String.class)
+ );
+ }
+ }
+}
diff --git a/examples/employee-app/src/main/java/io/helidon/examples/employee/EmployeeService.java b/examples/employee-app/src/main/java/io/helidon/examples/employee/EmployeeService.java
new file mode 100644
index 000000000..8bec0e7ba
--- /dev/null
+++ b/examples/employee-app/src/main/java/io/helidon/examples/employee/EmployeeService.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.employee;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import io.helidon.config.Config;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+/**
+ * The Employee service endpoints.
+ *
+ * Get all employees: {@code curl -X GET http://localhost:8080/employees}
+ * Get employee by id: {@code curl -X GET http://localhost:8080/employees/{id}}
+ * Add employee {@code curl -X POST http://localhost:8080/employees/{id}}
+ * Update employee by id {@code curl -X PUT http://localhost:8080/employees/{id}}
+ * Delete employee by id {@code curl -X DELETE http://localhost:8080/employees/{id}}
+ *
+ * The message is returned as a JSON object
+ */
+public class EmployeeService implements HttpService {
+ private final EmployeeRepository employees;
+ private static final Logger LOGGER = Logger.getLogger(EmployeeService.class.getName());
+
+ EmployeeService(Config config) {
+ String driverType = config.get("app.drivertype").asString().orElse("Array");
+ employees = EmployeeRepository.create(driverType, config);
+ }
+
+ /**
+ * A service registers itself by updating the routine rules.
+ *
+ * @param rules the routing rules.
+ */
+ @Override
+ public void routing(HttpRules rules) {
+ rules.get("/", this::getAll)
+ .get("/lastname/{name}", this::getByLastName)
+ .get("/department/{name}", this::getByDepartment)
+ .get("/title/{name}", this::getByTitle)
+ .post("/", this::save)
+ .get("/{id}", this::getEmployeeById)
+ .put("/{id}", this::update)
+ .delete("/{id}", this::delete);
+ }
+
+ /**
+ * Gets all the employees.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void getAll(ServerRequest request, ServerResponse response) {
+ LOGGER.fine("getAll");
+
+ response.send(employees.getAll());
+ }
+
+ /**
+ * Gets the employees by the last name specified in the parameter.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void getByLastName(ServerRequest request, ServerResponse response) {
+ LOGGER.fine("getByLastName");
+
+ String name = request.path().pathParameters().get("name");
+ // Invalid query strings handled in isValidQueryStr. Keeping DRY
+ if (isValidQueryStr(response, name)) {
+ response.send(employees.getByLastName(name));
+ }
+ }
+
+ /**
+ * Gets the employees by the title specified in the parameter.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void getByTitle(ServerRequest request, ServerResponse response) {
+ LOGGER.fine("getByTitle");
+
+ String title = request.path().pathParameters().get("name");
+ if (isValidQueryStr(response, title)) {
+ response.send(employees.getByTitle(title));
+ }
+ }
+
+ /**
+ * Gets the employees by the department specified in the parameter.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void getByDepartment(ServerRequest request, ServerResponse response) {
+ LOGGER.fine("getByDepartment");
+
+ String department = request.path().pathParameters().get("name");
+ if (isValidQueryStr(response, department)) {
+ response.send(employees.getByDepartment(department));
+ }
+ }
+
+ /**
+ * Gets the employees by the ID specified in the parameter.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void getEmployeeById(ServerRequest request, ServerResponse response) {
+ LOGGER.fine("getEmployeeById");
+
+ String id = request.path().pathParameters().get("id");
+ // If invalid, response handled in isValidId. Keeping DRY
+ if (isValidQueryStr(response, id)) {
+ employees.getById(id)
+ .ifPresentOrElse(response::send, () -> response.status(404).send());
+ }
+ }
+
+ /**
+ * Saves a new employee.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void save(ServerRequest request, ServerResponse response) {
+ LOGGER.fine("save");
+
+ Employee employee = request.content().as(Employee.class);
+ employees.save(Employee.of(null,
+ employee.getFirstName(),
+ employee.getLastName(),
+ employee.getEmail(),
+ employee.getPhone(),
+ employee.getBirthDate(),
+ employee.getTitle(),
+ employee.getDepartment()));
+ response.status(201).send();
+ }
+
+ /**
+ * Updates an existing employee.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void update(ServerRequest request, ServerResponse response) {
+ LOGGER.fine("update");
+
+ String id = request.path().pathParameters().get("id");
+ if (isValidQueryStr(response, id)) {
+ if (employees.update(request.content().as(Employee.class), id) == 0) {
+ response.status(404).send();
+ } else {
+ response.status(204).send();
+ }
+ }
+ }
+
+ /**
+ * Deletes an existing employee.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void delete(ServerRequest request, ServerResponse response) {
+ LOGGER.fine("delete");
+
+ String id = request.path().pathParameters().get("id");
+ if (isValidQueryStr(response, id)) {
+ if (employees.deleteById(id) == 0) {
+ response.status(404).send();
+ } else {
+ response.status(204).send();
+ }
+ }
+ }
+
+ /**
+ * Validates the parameter.
+ *
+ * @param response the server response
+ * @param name employee name
+ * @return true if valid, false otherwise
+ */
+ private boolean isValidQueryStr(ServerResponse response, String name) {
+ Map errorMessage = new HashMap<>();
+ if (name == null || name.isEmpty() || name.length() > 100) {
+ errorMessage.put("errorMessage", "Invalid query string");
+ response.status(400).send(errorMessage);
+ return false;
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/examples/employee-app/src/main/java/io/helidon/examples/employee/Main.java b/examples/employee-app/src/main/java/io/helidon/examples/employee/Main.java
new file mode 100644
index 000000000..77b8d0929
--- /dev/null
+++ b/examples/employee-app/src/main/java/io/helidon/examples/employee/Main.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.employee;
+
+import io.helidon.config.Config;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.WebServerConfig;
+import io.helidon.webserver.http.HttpRouting;
+import io.helidon.webserver.staticcontent.StaticContentService;
+
+/**
+ * Simple Employee rest application.
+ */
+public final class Main {
+
+ /**
+ * Cannot be instantiated.
+ */
+ private Main() {
+ }
+
+ /**
+ * Application main entry point.
+ *
+ * @param args command line arguments.
+ */
+ public static void main(final String[] args) {
+ WebServerConfig.Builder builder = WebServer.builder();
+ setup(builder);
+ WebServer server = builder.build().start();
+ System.out.printf("""
+ WEB server is up!
+ Web client at: http://localhost:%1$d/public/index.html
+ """, server.port());
+ }
+
+ /**
+ * Set up the server.
+ *
+ * @param server server builder
+ */
+ static void setup(WebServerConfig.Builder server) {
+
+ // load logging configuration
+ LogConfig.configureRuntime();
+
+ // 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 and JSON support registration
+ server.config(config.get("server"))
+ .routing(r -> routing(r, config));
+ }
+
+ /**
+ * Setup routing.
+ *
+ * @param routing routing builder
+ * @param config configuration of this server
+ */
+ static void routing(HttpRouting.Builder routing, Config config) {
+ routing.register("/public", StaticContentService.builder("public")
+ .welcomeFileName("index.html"))
+ .register("/employees", new EmployeeService(config));
+ }
+
+}
diff --git a/examples/employee-app/src/main/java/io/helidon/examples/employee/package-info.java b/examples/employee-app/src/main/java/io/helidon/examples/employee/package-info.java
new file mode 100644
index 000000000..aa7f6ef0c
--- /dev/null
+++ b/examples/employee-app/src/main/java/io/helidon/examples/employee/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Employee example application
+ *
+ * Start with {@link io.helidon.examples.employee.Main} class.
+ *
+ * @see io.helidon.examples.employee.Main
+ */
+package io.helidon.examples.employee;
diff --git a/examples/employee-app/src/main/resources/application.yaml b/examples/employee-app/src/main/resources/application.yaml
new file mode 100644
index 000000000..946d4854a
--- /dev/null
+++ b/examples/employee-app/src/main/resources/application.yaml
@@ -0,0 +1,29 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+app:
+ # database user, such as "system"
+ user:
+ # database password
+ password:
+ # host url - remove to use the default of Oracle XE (localhost:1521/XE)
+ hosturl: :/.
+ # switch to "Database" to use database. Uses in-memory structure otherwise
+ drivertype: Array
+
+server:
+ port: 8080
+ host: 0.0.0.0
diff --git a/examples/employee-app/src/main/resources/employees.json b/examples/employee-app/src/main/resources/employees.json
new file mode 100644
index 000000000..bce81a67c
--- /dev/null
+++ b/examples/employee-app/src/main/resources/employees.json
@@ -0,0 +1,402 @@
+[
+ {
+ "id": "84240085-7c68-4930-8fb3-2f9be11b6810",
+ "firstName": "Hugh",
+ "lastName": "Jast",
+ "email": "Hugh.Jast@example.com",
+ "phone": "730-715-4446",
+ "birthDate": "1970-11-28",
+ "title": "National Data Strategist",
+ "department": "Mobility"
+ },
+ {
+ "id": "f9971d4f-5b30-4553-8d5c-2116f9a58264",
+ "firstName": "Toy",
+ "lastName": "Herzog",
+ "email": "Toy.Herzog@example.com",
+ "phone": "769-569-1789",
+ "birthDate": "1961-08-08",
+ "title": "Dynamic Operations Manager",
+ "department": "Paradigm"
+ },
+ {
+ "id": "5b8a19f4-8ccf-49b2-9d68-9644c29eb0f0",
+ "firstName": "Reed",
+ "lastName": "Hahn",
+ "email": "Reed.Hahn@example.com",
+ "phone": "429-071-2018",
+ "birthDate": "1977-02-05",
+ "title": "Future Directives Facilitator",
+ "department": "Quality"
+ },
+ {
+ "id": "07700c37-a418-4762-9d7e-831dc1ea797e",
+ "firstName": "Novella",
+ "lastName": "Bahringer",
+ "email": "Novella.Bahringer@example.com",
+ "phone": "293-596-3547",
+ "birthDate": "1961-07-25",
+ "title": "Principal Factors Architect",
+ "department": "Division"
+ },
+ {
+ "id": "11c9cf10-fbbd-4ffa-8ef6-038a4bce9713",
+ "firstName": "Zora",
+ "lastName": "Sawayn",
+ "email": "Zora.Sawayn@example.com",
+ "phone": "923-814-0502",
+ "birthDate": "1978-03-18",
+ "title": "Dynamic Marketing Designer",
+ "department": "Security"
+ },
+ {
+ "id": "19737839-97a8-4b07-b52b-828db9f98e0a",
+ "firstName": "Cordia",
+ "lastName": "Willms",
+ "email": "Cordia.Willms@example.com",
+ "phone": "778-821-3941",
+ "birthDate": "1989-03-31",
+ "title": "Human Division Representative",
+ "department": "Optimization"
+ },
+ {
+ "id": "3e8822ea-6a3d-4855-9e79-d918c7733ec5",
+ "firstName": "Clair",
+ "lastName": "Bartoletti",
+ "email": "Clair.Bartoletti@example.com",
+ "phone": "647-896-8993",
+ "birthDate": "1986-01-04",
+ "title": "Customer Marketing Executive",
+ "department": "Factors"
+ },
+ {
+ "id": "2937368c-2dd9-4e86-908c-4a39a27bde39",
+ "firstName": "Joe",
+ "lastName": "Beahan",
+ "email": "Joe.Beahan@example.com",
+ "phone": "548-890-6181",
+ "birthDate": "1990-07-11",
+ "title": "District Group Specialist",
+ "department": "Program"
+ },
+ {
+ "id": "e74f77d9-69f2-48ac-8a7f-b90a70bdeeea",
+ "firstName": "Daphney",
+ "lastName": "Feest",
+ "email": "Daphney.Feest@example.com",
+ "phone": "143-967-7041",
+ "birthDate": "1984-03-26",
+ "title": "Dynamic Mobility Consultant",
+ "department": "Metrics"
+ },
+ {
+ "id": "154df9ce-1e86-47e1-a770-306a26cd62e9",
+ "firstName": "Janessa",
+ "lastName": "Wyman",
+ "email": "Janessa.Wyman@example.com",
+ "phone": "498-186-9009",
+ "birthDate": "1985-09-06",
+ "title": "Investor Brand Associate",
+ "department": "Markets"
+ },
+ {
+ "id": "92c8b250-786d-47eb-a7f5-eabe3d6e39c2",
+ "firstName": "Mara",
+ "lastName": "Roob",
+ "email": "Mara.Roob@example.com",
+ "phone": "962-540-9884",
+ "birthDate": "1975-05-11",
+ "title": "Legacy Assurance Engineer",
+ "department": "Usability"
+ },
+ {
+ "id": "9e046988-c561-417f-9696-f14608c00bd4",
+ "firstName": "Brennon",
+ "lastName": "Bernhard",
+ "email": "Brennon.Bernhard@example.com",
+ "phone": "395-224-9853",
+ "birthDate": "1963-12-05",
+ "title": "Product Web Officer",
+ "department": "Interactions"
+ },
+ {
+ "id": "490a1262-15a7-4606-84ae-6e02f6671c13",
+ "firstName": "Amya",
+ "lastName": "Bernier",
+ "email": "Amya.Bernier@example.com",
+ "phone": "563-562-4171",
+ "birthDate": "1972-06-23",
+ "title": "Global Tactics Planner",
+ "department": "Program"
+ },
+ {
+ "id": "60bad2bd-a71d-4494-bff1-09940809c51c",
+ "firstName": "Claud",
+ "lastName": "Boehm",
+ "email": "Claud.Boehm@example.com",
+ "phone": "089-073-7399",
+ "birthDate": "1965-02-27",
+ "title": "National Marketing Associate",
+ "department": "Directives"
+ },
+ {
+ "id": "0f48efc9-a820-42c0-9d8f-2ae962d0ce7b",
+ "firstName": "Nyah",
+ "lastName": "Schowalter",
+ "email": "Nyah.Schowalter@example.com",
+ "phone": "823-063-7120",
+ "birthDate": "1969-02-19",
+ "title": "Dynamic Communications Assistant",
+ "department": "Metrics"
+ },
+ {
+ "id": "3323fc7a-dcb9-4cfe-9e59-4160290c1ccc",
+ "firstName": "Imogene",
+ "lastName": "Bernhard",
+ "email": "Imogene.Bernhard@example.com",
+ "phone": "747-970-6062",
+ "birthDate": "1958-02-09",
+ "title": "Dynamic Assurance Supervisor",
+ "department": "Paradigm"
+ },
+ {
+ "id": "44c5fe14-d1a3-416a-aab8-f569c3ed2eca",
+ "firstName": "Chanel",
+ "lastName": "Kuhlman",
+ "email": "Chanel.Kuhlman@example.com",
+ "phone": "882-145-9513",
+ "birthDate": "1985-03-03",
+ "title": "District Paradigm Representative",
+ "department": "Integration"
+ },
+ {
+ "id": "8235e91e-3024-47f8-afd8-577617cfb8bf",
+ "firstName": "Cierra",
+ "lastName": "Morar",
+ "email": "Cierra.Morar@example.com",
+ "phone": "273-607-2209",
+ "birthDate": "1965-01-25",
+ "title": "Dynamic Data Planner",
+ "department": "Paradigm"
+ },
+ {
+ "id": "5733bed9-ce41-4ed5-8b29-9288318dd5a7",
+ "firstName": "Faye",
+ "lastName": "Grimes",
+ "email": "Faye.Grimes@example.com",
+ "phone": "750-139-1344",
+ "birthDate": "1962-08-21",
+ "title": "Central Applications Analyst",
+ "department": "Tactics"
+ },
+ {
+ "id": "34956311-26fb-46b1-abf0-a95a08d929ea",
+ "firstName": "Doyle",
+ "lastName": "Rohan",
+ "email": "Doyle.Rohan@example.com",
+ "phone": "093-457-5621",
+ "birthDate": "1991-11-29",
+ "title": "Corporate Accountability Supervisor",
+ "department": "Applications"
+ },
+ {
+ "id": "2e121065-431a-4ba2-aeb4-3919fcb7969a",
+ "firstName": "Jonathan",
+ "lastName": "Barrows",
+ "email": "Jonathan.Barrows@example.com",
+ "phone": "262-503-2161",
+ "birthDate": "1963-12-15",
+ "title": "Regional Configuration Liason",
+ "department": "Implementation"
+ },
+ {
+ "id": "99d0603f-03cc-4dca-bebe-a711eaf14d77",
+ "firstName": "Myriam",
+ "lastName": "Luettgen",
+ "email": "Myriam.Luettgen@example.com",
+ "phone": "951-924-8295",
+ "birthDate": "1962-02-08",
+ "title": "Central Functionality Specialist",
+ "department": "Accountability"
+ },
+ {
+ "id": "50653f57-3379-4bfd-9999-2ec86ab1131d",
+ "firstName": "Johnnie",
+ "lastName": "Schiller",
+ "email": "Johnnie.Schiller@example.com",
+ "phone": "534-025-2200",
+ "birthDate": "1965-04-11",
+ "title": "Principal Creative Developer",
+ "department": "Interactions"
+ },
+ {
+ "id": "d93c7987-69c8-4333-99fe-d1561b338722",
+ "firstName": "Laila",
+ "lastName": "White",
+ "email": "Laila.White@example.com",
+ "phone": "468-939-2298",
+ "birthDate": "1956-01-04",
+ "title": "Corporate Optimization Engineer",
+ "department": "Assurance"
+ },
+ {
+ "id": "ac90bb96-79e1-424a-94f6-90f7c01ea4b3",
+ "firstName": "Alessandra",
+ "lastName": "Becker",
+ "email": "Alessandra.Becker@example.com",
+ "phone": "081-724-0866",
+ "birthDate": "1984-08-12",
+ "title": "Central Identity Associate",
+ "department": "Quality"
+ },
+ {
+ "id": "215f86fb-8434-4b75-8cc2-4bf731b71f6f",
+ "firstName": "Shannon",
+ "lastName": "McCullough",
+ "email": "Shannon.McCullough@example.com",
+ "phone": "101-995-1089",
+ "birthDate": "1989-02-25",
+ "title": "Global Data Engineer",
+ "department": "Division"
+ },
+ {
+ "id": "f09f164c-7fc2-4afe-b9ae-29bc1c48b529",
+ "firstName": "Garnet",
+ "lastName": "Labadie",
+ "email": "Garnet.Labadie@example.com",
+ "phone": "147-954-6624",
+ "birthDate": "1990-01-01",
+ "title": "Senior Communications Producer",
+ "department": "Program"
+ },
+ {
+ "id": "b16d7d13-e4ee-4293-bb2d-c79d98485ef0",
+ "firstName": "Mark",
+ "lastName": "Graham",
+ "email": "Mark.Graham@example.com",
+ "phone": "462-746-7388",
+ "birthDate": "1991-08-23",
+ "title": "Legacy Directives Agent",
+ "department": "Assurance"
+ },
+ {
+ "id": "3ff0adc3-09e0-4490-900c-9d59dd622bd8",
+ "firstName": "Tristin",
+ "lastName": "Bayer",
+ "email": "Tristin.Bayer@example.com",
+ "phone": "882-044-3996",
+ "birthDate": "1964-03-26",
+ "title": "Internal Marketing Officer",
+ "department": "Intranet"
+ },
+ {
+ "id": "c1e5e2ad-2ee9-4eb1-af9e-e9e17fe694bc",
+ "firstName": "Maurice",
+ "lastName": "Renner",
+ "email": "Maurice.Renner@example.com",
+ "phone": "383-435-0943",
+ "birthDate": "1973-11-05",
+ "title": "National Accountability Planner",
+ "department": "Accounts"
+ },
+ {
+ "id": "06a0ac23-ff09-4f27-971d-1b901e5ff1c8",
+ "firstName": "Preston",
+ "lastName": "Stark",
+ "email": "Preston.Stark@example.com",
+ "phone": "080-698-9552",
+ "birthDate": "1994-02-02",
+ "title": "Corporate Program Orchestrator",
+ "department": "Integration"
+ },
+ {
+ "id": "2443a803-39ec-435b-9bda-1bfdee716308",
+ "firstName": "Mabelle",
+ "lastName": "Herman",
+ "email": "Mabelle.Herman@example.com",
+ "phone": "778-672-2787",
+ "birthDate": "1956-11-30",
+ "title": "Human Identity Officer",
+ "department": "Integration"
+ },
+ {
+ "id": "45d87394-7fb2-430a-9b65-714b5266b8a1",
+ "firstName": "Juvenal",
+ "lastName": "Swaniawski",
+ "email": "Juvenal.Swaniawski@example.com",
+ "phone": "349-906-2745",
+ "birthDate": "1963-11-17",
+ "title": "Future Program Planner",
+ "department": "Response"
+ },
+ {
+ "id": "f0b4ec27-af12-4694-aee2-77a620c6fb5e",
+ "firstName": "Rachelle",
+ "lastName": "Okuneva",
+ "email": "Rachelle.Okuneva@example.com",
+ "phone": "134-565-3868",
+ "birthDate": "1992-05-27",
+ "title": "District Creative Architect",
+ "department": "Paradigm"
+ },
+ {
+ "id": "7c4dc3be-6141-4ce2-8513-cee11e2708d1",
+ "firstName": "Macey",
+ "lastName": "Weissnat",
+ "email": "Macey.Weissnat@example.com",
+ "phone": "210-461-3749",
+ "birthDate": "1978-06-24",
+ "title": "Product Accountability Facilitator",
+ "department": "Data"
+ },
+ {
+ "id": "c4282cf5-f10b-4752-b201-a1f6cb469212",
+ "firstName": "Ena",
+ "lastName": "Gerlach",
+ "email": "Ena.Gerlach@example.com",
+ "phone": "429-925-7634",
+ "birthDate": "1976-04-09",
+ "title": "Human Tactics Agent",
+ "department": "Creative"
+ },
+ {
+ "id": "2b32bbbd-c4bb-426c-a759-1664f40db4c8",
+ "firstName": "Darrick",
+ "lastName": "Deckow",
+ "email": "Darrick.Deckow@example.com",
+ "phone": "549-222-4121",
+ "birthDate": "1956-10-25",
+ "title": "Lead Solutions Producer",
+ "department": "Metrics"
+ },
+ {
+ "id": "65389ce1-d930-48ae-9347-49741496dc4e",
+ "firstName": "Palma",
+ "lastName": "Torp",
+ "email": "Palma.Torp@example.com",
+ "phone": "346-556-3517",
+ "birthDate": "1967-06-24",
+ "title": "Product Infrastructure Consultant",
+ "department": "Response"
+ },
+ {
+ "id": "9cbaf350-0e00-4aaf-84a4-b348f75257ca",
+ "firstName": "Lucious",
+ "lastName": "Steuber",
+ "email": "Lucious.Steuber@example.com",
+ "phone": "977-372-2840",
+ "birthDate": "1961-11-24",
+ "title": "District Creative Supervisor",
+ "department": "Mobility"
+ },
+ {
+ "id": "72de2181-3f3d-4b0d-a528-bc60a1aa0a12",
+ "firstName": "Kacey",
+ "lastName": "Kilback",
+ "email": "Kacey.Kilback@example.com",
+ "phone": "268-777-2011",
+ "birthDate": "1957-09-06",
+ "title": "Corporate Mobility Agent",
+ "department": "Infrastructure"
+ }
+]
\ No newline at end of file
diff --git a/examples/employee-app/src/main/resources/logging.properties b/examples/employee-app/src/main/resources/logging.properties
new file mode 100644
index 000000000..110246353
--- /dev/null
+++ b/examples/employee-app/src/main/resources/logging.properties
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+# Component specific log levels
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
diff --git a/examples/employee-app/src/main/resources/public/EmployeeController.js b/examples/employee-app/src/main/resources/public/EmployeeController.js
new file mode 100644
index 000000000..26d2d0e56
--- /dev/null
+++ b/examples/employee-app/src/main/resources/public/EmployeeController.js
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var server = "/";
+
+function search (){
+ var searchTerm = $("#searchText").val().trim();
+ if (searchTerm != "") {
+ $("#people").show();
+ $("#people").html("SEARCHING...");
+ $.ajax({
+ url: server + "employees/" +
+ $("#searchType").val() + "/" +
+ encodeURIComponent(searchTerm),
+ method: "GET"
+ }).done(
+ function(data) {
+ $("#people").empty();
+ $("#people").hide();
+ if (data.length == 0) {
+ $("#people").html("");
+ $("#notFound").show();
+ $("#notFound").html("No people found matching your search criteria");
+ } else {
+ showResults(data);
+ }
+ $("#people").show(400, "swing");
+ });
+ } else {
+ loadEmployees();
+ }
+}
+
+$(function() {
+ $("#searchText").on("keyup", function(e) {
+ if (e.keyCode == 13) {
+ search ();
+ }
+ });
+});
+
+function showResults(data){
+ $("#people").hide();
+ $("#people").empty();
+ $("#notFound").hide();
+ data.forEach(function(employee) {
+ var item = $(renderEmployees(employee));
+ item.on("click", function() {
+ var detailItem = $(renderDetailEmployee(employee));
+ $("#home").hide();
+ $("#detail").empty();
+ $("#notFound").hide();
+ $("#detail").append(detailItem);
+ $("#people").hide(
+ 400,
+ "swing",
+ function() {
+ $("#detail").show()
+ });
+ });
+ $("#people").append(item);
+ });
+}
+
+function showEmployeeForm() {
+ $("#notFound").hide();
+ $("#editForm").hide();
+ $("#deleteButton").hide();
+ $("#employeeForm").show();
+ $("#formTitle").text("Add Employee");
+ $("#home").hide();
+ $("#people").hide();
+}
+
+function loadEmployees() {
+ $("#notFound").hide();
+ $("#searchText").val("");
+ $("#employeeForm").hide();
+ $("#editForm").hide();
+ $("#home").show();
+ $("#people").show();
+ $("#people").html("LOADING...");
+ $.ajax({
+ dataType: "json",
+ url: server + "employees",
+ method: "GET"
+ }).done(function(data) {
+ showResults(data);
+ $("#people").show(400, "swing");
+ });
+}
+
+
+function renderEmployees(employee){
+ var template = $('#employees_tpl').html();
+ Mustache.parse(template);
+ var rendered = Mustache.render(template, {
+ "firstName" : employee.firstName,
+ "lastName" : employee.lastName,
+ "title" : employee.title,
+ "department" : employee.department
+ });
+ return rendered;
+}
+
+function renderDetailEmployee(employee){
+ var template = $('#detail_tpl').html();
+ Mustache.parse(template);
+ var rendered = Mustache.render(template,{
+ "id" : employee.id,
+ "firstName" : employee.firstName,
+ "lastName" : employee.lastName,
+ "email" : employee.email,
+ "birthDate" : employee.birthDate,
+ "phone" : employee.phone,
+ "title" : employee.title,
+ "department" : employee.department
+ });
+ return rendered;
+}
+
+function save() {
+ var employee = {
+ id: "",
+ firstName: $("#firstName").val(),
+ lastName: $("#lastName").val(),
+ email: $("#email").val(),
+ phone: $("#phone").val(),
+ birthDate: $("#birthDate").val(),
+ title: $("#title").val(),
+ department: $("#department").val()
+ };
+ $.ajax({
+ url: server + "employees",
+ method: "POST",
+ data: JSON.stringify(employee)
+ }).done(function(data) {
+ $("#detail").hide();
+ $("#firstName").val("");
+ $("#lastName").val("");
+ $("#email").val("");
+ $("#phone").val("");
+ $("#birthDate").val("");
+ $("#title").val("");
+ $("#department").val("");
+ loadEmployees();
+ });
+
+}
+
+function updateEmployee() {
+ var employee = {
+ id: $("#editId").val(),
+ firstName: $("#editFirstName").val(),
+ lastName: $("#editLastName").val(),
+ email: $("#editEmail").val(),
+ phone: $("#editPhone").val(),
+ birthDate: $("#editBirthDate").val(),
+ title: $("#editTitle").val(),
+ department: $("#editDepartment").val()
+ };
+ $("#detail").html("UPDATING...");
+ $.ajax({
+ url: server + "employees/" + employee.id,
+ method: "PUT",
+ data: JSON.stringify(employee)
+ }).done(function(data) {
+ $("#detail").hide();
+ loadEmployees();
+ });
+}
+
+function deleteEmployee() {
+ var employee = {
+ firstName: $("#editFirstName").val(),
+ lastName: $("#editLastName").val(),
+ id: $("#editId").val()
+ };
+ $('
').dialog({
+ modal: true,
+ title: "Confirm Delete",
+ open: function() {
+ var markup = 'Are you sure you want to delete ' +
+ employee.firstName + ' ' + employee.lastName +
+ " employee?";
+ $(this).html(markup);
+ },
+ buttons: {
+ Ok: function() {
+ $("#detail").html("DELETING...");
+ $(this).dialog("close");
+ $.ajax({
+ url: server + "employees/" + employee.id,
+ method: "DELETE"
+ }).done(function(data) {
+ $("#detail").hide();
+ loadEmployees();
+ });
+ },
+ Cancel: function() {
+ $(this).dialog("close");
+ }
+ }
+ });
+
+}
diff --git a/examples/employee-app/src/main/resources/public/index.html b/examples/employee-app/src/main/resources/public/index.html
new file mode 100644
index 000000000..7351e33c0
--- /dev/null
+++ b/examples/employee-app/src/main/resources/public/index.html
@@ -0,0 +1,399 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cloud Employee App
+
+
+
+
+
+
+ LOADING...
+
+
+
+
+
+
diff --git a/examples/employee-app/src/main/resources/public/nopic.png b/examples/employee-app/src/main/resources/public/nopic.png
new file mode 100644
index 000000000..5967653cf
Binary files /dev/null and b/examples/employee-app/src/main/resources/public/nopic.png differ
diff --git a/examples/employee-app/src/test/java/io/helidon/examples/employee/MainTest.java b/examples/employee-app/src/test/java/io/helidon/examples/employee/MainTest.java
new file mode 100644
index 000000000..60ba7eb89
--- /dev/null
+++ b/examples/employee-app/src/test/java/io/helidon/examples/employee/MainTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.employee;
+
+import io.helidon.config.Config;
+import io.helidon.http.Status;
+import io.helidon.webclient.http1.Http1ClientResponse;
+import io.helidon.webserver.http.HttpRouting;
+import io.helidon.webserver.testing.junit5.DirectClient;
+import io.helidon.webserver.testing.junit5.RoutingTest;
+import io.helidon.webserver.testing.junit5.SetUpRoute;
+
+import jakarta.json.JsonArray;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@RoutingTest
+public class MainTest {
+
+ private final DirectClient client;
+
+ public MainTest(DirectClient client) {
+ this.client = client;
+ }
+
+ @SetUpRoute
+ public static void setup(HttpRouting.Builder routing) {
+ Main.routing(routing, Config.empty());
+ }
+
+ @Test
+ public void testEmployees() {
+ try (Http1ClientResponse response = client.get("/employees")
+ .request()) {
+ assertThat("HTTP response2", response.status(), is(Status.OK_200));
+ assertThat(response.as(JsonArray.class).size(), is(40));
+ }
+ }
+
+}
diff --git a/examples/graphql/basics/README.md b/examples/graphql/basics/README.md
new file mode 100644
index 000000000..133e06f71
--- /dev/null
+++ b/examples/graphql/basics/README.md
@@ -0,0 +1,35 @@
+# Helidon GraphQL Basic Example
+
+This example shows the basics of using Helidon SE GraphQL. The example
+manually creates a GraphQL Schema using the [GraphQL Java](https://github.com/graphql-java/graphql-java) API.
+
+## Build and run
+
+Start the application:
+
+```shell
+mvn package
+java -jar target/helidon-examples-graphql-basics.jar
+```
+
+Note the port number reported by the application.
+
+Probe the GraphQL endpoints:
+
+1. Hello word endpoint:
+
+ ```shell
+ export PORT='41667'
+ curl -X POST http://127.0.0.1:${PORT}/graphql -d '{"query":"query { hello }"}'
+
+ #"data":{"hello":"world"}}
+ ```
+
+1. Hello in different languages
+
+ ```shell
+ export PORT='41667'
+ curl -X POST http://127.0.0.1:${PORT}/graphql -d '{"query":"query { helloInDifferentLanguages }"}'
+
+ #{"data":{"helloInDifferentLanguages":["Bonjour","Hola","Zdravstvuyte","Nǐn hǎo","Salve","Gudday","Konnichiwa","Guten Tag"]}}
+ ```
diff --git a/examples/graphql/basics/pom.xml b/examples/graphql/basics/pom.xml
new file mode 100644
index 000000000..0797fc6da
--- /dev/null
+++ b/examples/graphql/basics/pom.xml
@@ -0,0 +1,69 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.graphql
+ helidon-examples-graphql-basics
+ 1.0.0-SNAPSHOT
+ Helidon Examples GraphQL Basics
+
+
+ Basic usage of GraphQL in helidon SE
+
+
+
+ io.helidon.examples.graphql.basics.Main
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver-graphql
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.common
+ helidon-common
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
+
diff --git a/examples/graphql/basics/src/main/java/io/helidon/examples/graphql/basics/Main.java b/examples/graphql/basics/src/main/java/io/helidon/examples/graphql/basics/Main.java
new file mode 100644
index 000000000..1be76d499
--- /dev/null
+++ b/examples/graphql/basics/src/main/java/io/helidon/examples/graphql/basics/Main.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.graphql.basics;
+
+import java.util.List;
+
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.graphql.GraphQlService;
+
+import graphql.schema.DataFetcher;
+import graphql.schema.GraphQLSchema;
+import graphql.schema.StaticDataFetcher;
+import graphql.schema.idl.RuntimeWiring;
+import graphql.schema.idl.SchemaGenerator;
+import graphql.schema.idl.SchemaParser;
+import graphql.schema.idl.TypeDefinitionRegistry;
+
+/**
+ * Main class of Graphql SE integration example.
+ */
+@SuppressWarnings("SpellCheckingInspection")
+public class Main {
+
+ private Main() {
+ }
+
+ /**
+ * Start the example. Prints endpoints to standard output.
+ *
+ * @param args not used
+ */
+ public static void main(String[] args) {
+ WebServer server = WebServer.builder()
+ .routing(routing -> routing
+ .register(GraphQlService.create(buildSchema())))
+ .build();
+ server.start();
+ String endpoint = "http://localhost:" + server.port();
+ System.out.printf("""
+ GraphQL started on %1$s/graphql
+ GraphQL schema available on %1$s/graphql/schema.graphql
+ """, endpoint);
+ }
+
+ /**
+ * Generate a {@link GraphQLSchema}.
+ *
+ * @return a {@link GraphQLSchema}
+ */
+ private static GraphQLSchema buildSchema() {
+ String schema = """
+ type Query{
+ hello: String\s
+ helloInDifferentLanguages: [String]\s
+
+ }""";
+
+ SchemaParser schemaParser = new SchemaParser();
+ TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema);
+
+ // DataFetcher to return various hello's in difference languages
+ DataFetcher> dataFetcher = environment ->
+ List.of("Bonjour", "Hola", "Zdravstvuyte", "Nǐn hǎo", "Salve", "Gudday", "Konnichiwa", "Guten Tag");
+
+ RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
+ .type("Query", builder -> builder.dataFetcher("hello", new StaticDataFetcher("world")))
+ .type("Query", builder -> builder.dataFetcher("helloInDifferentLanguages", dataFetcher))
+ .build();
+
+ SchemaGenerator schemaGenerator = new SchemaGenerator();
+ return schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
+ }
+}
diff --git a/examples/graphql/basics/src/main/java/io/helidon/examples/graphql/basics/package-info.java b/examples/graphql/basics/src/main/java/io/helidon/examples/graphql/basics/package-info.java
new file mode 100644
index 000000000..23b3ca705
--- /dev/null
+++ b/examples/graphql/basics/src/main/java/io/helidon/examples/graphql/basics/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Example of healthchecks in helidon SE.
+ */
+package io.helidon.examples.graphql.basics;
diff --git a/examples/graphql/pom.xml b/examples/graphql/pom.xml
new file mode 100644
index 000000000..5df98496d
--- /dev/null
+++ b/examples/graphql/pom.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ 4.0.0
+
+ helidon-examples-project
+ io.helidon.examples
+ 1.0.0-SNAPSHOT
+
+ io.helidon.examples.graphql
+ helidon-examples-graphql-project
+ 1.0.0-SNAPSHOT
+ pom
+ Helidon Examples GraphQL
+
+
+ basics
+
+
diff --git a/examples/health/basics/README.md b/examples/health/basics/README.md
new file mode 100644
index 000000000..17c690051
--- /dev/null
+++ b/examples/health/basics/README.md
@@ -0,0 +1,23 @@
+# Helidon Health Basic Example
+
+This example shows the basics of using Helidon SE Health. It uses the
+set of built-in health checks that Helidon provides plus defines a
+custom health check.
+
+## Build and run
+
+Start the application:
+
+```shell
+mvn package
+java -jar target/helidon-examples-health-basics.jar
+```
+
+Note the port number reported by the application.
+
+Probe the health endpoints:
+
+```shell
+curl -i -X GET http://localhost:8080/observe/health/ready
+curl -i -X GET http://localhost:8080/observe/health/
+```
diff --git a/examples/health/basics/pom.xml b/examples/health/basics/pom.xml
new file mode 100644
index 000000000..1d7c48b12
--- /dev/null
+++ b/examples/health/basics/pom.xml
@@ -0,0 +1,79 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.health
+ helidon-examples-health-basics
+ 1.0.0-SNAPSHOT
+ Helidon Examples Health Basics
+
+
+ Basic usage of health checks in helidon SE
+
+
+
+ io.helidon.examples.health.basics.Main
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-health
+
+
+ io.helidon.health
+ helidon-health-checks
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
+
diff --git a/examples/health/basics/src/main/java/io/helidon/examples/health/basics/Main.java b/examples/health/basics/src/main/java/io/helidon/examples/health/basics/Main.java
new file mode 100644
index 000000000..e3b5ef5d7
--- /dev/null
+++ b/examples/health/basics/src/main/java/io/helidon/examples/health/basics/Main.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2018, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.health.basics;
+
+import java.time.Duration;
+
+import io.helidon.health.HealthCheckResponse;
+import io.helidon.health.HealthCheckType;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.http.HttpRouting;
+import io.helidon.webserver.observe.ObserveFeature;
+import io.helidon.webserver.observe.health.HealthObserver;
+
+/**
+ * Main class of health check integration example.
+ */
+public final class Main {
+
+ private static long serverStartTime;
+
+ private Main() {
+ }
+
+ /**
+ * Start the example. Prints endpoints to standard output.
+ *
+ * @param args not used
+ */
+ public static void main(String[] args) {
+ serverStartTime = System.currentTimeMillis();
+
+ // load logging
+ LogConfig.configureRuntime();
+
+ ObserveFeature observe = ObserveFeature.builder()
+ .observersDiscoverServices(true)
+ .addObserver(HealthObserver.builder()
+ .details(true)
+ .useSystemServices(true)
+ .addCheck(() -> HealthCheckResponse.builder()
+ .status(HealthCheckResponse.Status.UP)
+ .detail("time", System.currentTimeMillis())
+ .build(), HealthCheckType.READINESS)
+ .addCheck(() -> HealthCheckResponse.builder()
+ .status(isStarted())
+ .detail("time", System.currentTimeMillis())
+ .build(), HealthCheckType.STARTUP)
+ .build())
+ .build();
+
+ WebServer server = WebServer.builder()
+ .featuresDiscoverServices(false)
+ .addFeature(observe)
+ .routing(Main::routing)
+ .port(8080)
+ .build()
+ .start();
+
+ System.out.println("WEB server is up! http://localhost:" + server.port());
+ }
+
+ /**
+ * Set up HTTP routing.
+ * This method is used from tests as well.
+ *
+ * @param router HTTP routing builder
+ */
+ static void routing(HttpRouting.Builder router) {
+ router.get("/hello", (req, res) -> res.send("Hello World!"));
+ }
+
+ private static boolean isStarted() {
+ return Duration.ofMillis(System.currentTimeMillis() - serverStartTime).getSeconds() >= 8;
+ }
+}
diff --git a/examples/health/basics/src/main/java/io/helidon/examples/health/basics/package-info.java b/examples/health/basics/src/main/java/io/helidon/examples/health/basics/package-info.java
new file mode 100644
index 000000000..f59115091
--- /dev/null
+++ b/examples/health/basics/src/main/java/io/helidon/examples/health/basics/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2018, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Example of healthchecks in helidon SE.
+ */
+package io.helidon.examples.health.basics;
diff --git a/examples/health/pom.xml b/examples/health/pom.xml
new file mode 100644
index 000000000..bb494a032
--- /dev/null
+++ b/examples/health/pom.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ 4.0.0
+
+ helidon-examples-project
+ io.helidon.examples
+ 1.0.0-SNAPSHOT
+
+ io.helidon.examples.health
+ helidon-examples-health-project
+ 1.0.0-SNAPSHOT
+ pom
+ Helidon Examples Health
+
+
+ basics
+
+
diff --git a/examples/integrations/README.md b/examples/integrations/README.md
new file mode 100644
index 000000000..05fd10c9d
--- /dev/null
+++ b/examples/integrations/README.md
@@ -0,0 +1 @@
+# Helidon Integrations Examples
diff --git a/examples/integrations/cdi/README.md b/examples/integrations/cdi/README.md
new file mode 100644
index 000000000..64fad7955
--- /dev/null
+++ b/examples/integrations/cdi/README.md
@@ -0,0 +1 @@
+# Helidon Integrations CDI Examples
diff --git a/examples/integrations/cdi/datasource-hikaricp-h2/README.md b/examples/integrations/cdi/datasource-hikaricp-h2/README.md
new file mode 100644
index 000000000..d630d6a6b
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp-h2/README.md
@@ -0,0 +1,19 @@
+# H2 Integration Example
+
+## Overview
+
+This example shows a trivial Helidon MicroProfile application that
+uses the Hikari connection pool CDI integration and an H2 in-memory
+database.
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-integrations-examples-datasource-hikaricp-h2.jar
+```
+
+Try the endpoint:
+```shell
+curl http://localhost:8080/tables
+```
diff --git a/examples/integrations/cdi/datasource-hikaricp-h2/pom.xml b/examples/integrations/cdi/datasource-hikaricp-h2/pom.xml
new file mode 100644
index 000000000..9f1f3275e
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp-h2/pom.xml
@@ -0,0 +1,103 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.integrations.cdi
+ helidon-integrations-examples-datasource-hikaricp-h2
+ 1.0.0-SNAPSHOT
+ Helidon Examples CDI Extensions DataSource/HikariCP H2
+
+
+
+
+ jakarta.enterprise
+ jakarta.enterprise.cdi-api
+ compile
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+ compile
+
+
+ org.eclipse.microprofile.config
+ microprofile-config-api
+ compile
+
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-datasource-hikaricp
+ runtime
+
+
+ io.smallrye
+ jandex
+ runtime
+ true
+
+
+ io.helidon.microprofile.server
+ helidon-microprofile-server
+ runtime
+
+
+ io.helidon.microprofile.config
+ helidon-microprofile-config
+ runtime
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
diff --git a/examples/integrations/cdi/datasource-hikaricp-h2/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/TablesResource.java b/examples/integrations/cdi/datasource-hikaricp-h2/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/TablesResource.java
new file mode 100644
index 000000000..4410a44cd
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp-h2/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/TablesResource.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.datasource.hikaricp.jaxrs;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Objects;
+
+import javax.sql.DataSource;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * A JAX-RS resource class in {@linkplain ApplicationScoped
+ * application scope} rooted at {@code /tables}.
+ *
+ * @see #get()
+ */
+@Path("/tables")
+@ApplicationScoped
+public class TablesResource {
+
+ private final DataSource dataSource;
+
+ /**
+ * Creates a new {@link TablesResource}.
+ *
+ * @param dataSource the {@link DataSource} to use to acquire
+ * database table names; must not be {@code null}
+ *
+ * @exception NullPointerException if {@code dataSource} is {@code
+ * null}
+ */
+ @Inject
+ public TablesResource(@Named("example") final DataSource dataSource) {
+ super();
+ this.dataSource = Objects.requireNonNull(dataSource);
+ }
+
+ /**
+ * Returns a {@link Response} which, if successful, contains a
+ * newline-separated list of Oracle database table names.
+ *
+ * This method never returns {@code null}.
+ *
+ * @return a non-{@code null} {@link Response}
+ *
+ * @exception SQLException if a database error occurs
+ */
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public Response get() throws SQLException {
+ final StringBuilder sb = new StringBuilder();
+ try (Connection connection = this.dataSource.getConnection();
+ PreparedStatement ps =
+ connection.prepareStatement(" SELECT TABLE_NAME"
+ + " FROM INFORMATION_SCHEMA.TABLES "
+ + "ORDER BY TABLE_NAME ASC");
+ ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ sb.append(rs.getString(1)).append("\n");
+ }
+ }
+ final Response returnValue = Response.ok()
+ .entity(sb.toString())
+ .build();
+ return returnValue;
+ }
+
+}
diff --git a/examples/integrations/cdi/datasource-hikaricp-h2/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/package-info.java b/examples/integrations/cdi/datasource-hikaricp-h2/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/package-info.java
new file mode 100644
index 000000000..1f72fcff0
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp-h2/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 JAX-RS-related classes and interfaces for this example
+ * project.
+ */
+package io.helidon.examples.integrations.datasource.hikaricp.jaxrs;
diff --git a/examples/integrations/cdi/datasource-hikaricp-h2/src/main/resources/META-INF/beans.xml b/examples/integrations/cdi/datasource-hikaricp-h2/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..a0938bff7
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp-h2/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/integrations/cdi/datasource-hikaricp-h2/src/main/resources/META-INF/microprofile-config.properties b/examples/integrations/cdi/datasource-hikaricp-h2/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 000000000..dcb470b29
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp-h2/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,25 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Default database properties.
+javax.sql.DataSource.example.dataSourceClassName = org.h2.jdbcx.JdbcDataSource
+javax.sql.DataSource.example.dataSource.url = jdbc:h2:mem:sample
+javax.sql.DataSource.example.dataSource.user = sa
+javax.sql.DataSource.example.dataSource.password =
+
+# Microprofile server properties
+server.port=8080
+server.host=0.0.0.0
diff --git a/examples/integrations/cdi/datasource-hikaricp-mysql/README.md b/examples/integrations/cdi/datasource-hikaricp-mysql/README.md
new file mode 100644
index 000000000..f1497e866
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp-mysql/README.md
@@ -0,0 +1,60 @@
+# MySQL Integration Example
+
+## Overview
+
+This example shows a trivial Helidon MicroProfile application that
+uses the MySQL CDI integration. It also shows how to run MySQL in a
+Docker container and connect to it using the application.
+
+## Notes
+
+To run MySQL's `mysql:8` Docker image in a Docker container named
+`mysql` that publishes its port 3306 to the host machine's port 3306
+and uses `tiger` as the MySQL root password and that will
+automatically be removed when it is stopped:
+
+```sh
+docker container run --rm -d -p 3306:3306 \
+ --env MYSQL_ROOT_PASSWORD=tiger \
+ --name mysql \
+ mysql:8
+```
+
+(Note that in the `3306:3306` option value above the first port number
+is the port number on the host (i.e. your physical machine running
+`docker`) and the second number (after the colon) is the port number
+on the Docker container.)
+
+To ensure that the sample application is configured to talk to MySQL
+running in this Docker container, verify that the following lines
+(among others) are present in
+`src/main/resources/META-INF/microprofile-config.properties`:
+
+```properties
+javax.sql.DataSource.example.dataSourceClassName=com.mysql.cj.jdbc.MysqlDataSource
+javax.sql.DataSource.example.dataSource.url = jdbc:mysql://localhost:3306
+javax.sql.DataSource.example.dataSource.user = root
+javax.sql.DataSource.example.dataSource.password = tiger
+```
+
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-integrations-examples-datasource-hikaricp-mysql.jar
+```
+
+Try the endpoint:
+```shell
+curl http://localhost:8080/tables
+```
+
+Stop the docker container:
+```shell
+docker stop mysql
+```
+
+## References
+
+- [MySQL Docker documentation](https://hub.docker.com/_/mysql?tab=description)
diff --git a/examples/integrations/cdi/datasource-hikaricp-mysql/pom.xml b/examples/integrations/cdi/datasource-hikaricp-mysql/pom.xml
new file mode 100644
index 000000000..d45df7492
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp-mysql/pom.xml
@@ -0,0 +1,104 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.integrations.cdi
+ helidon-integrations-examples-datasource-hikaricp-mysql
+ 1.0.0-SNAPSHOT
+ Helidon Examples CDI Extensions DataSource/HikariCP MySQL
+
+
+
+
+ jakarta.enterprise
+ jakarta.enterprise.cdi-api
+ compile
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+ compile
+
+
+ org.eclipse.microprofile.config
+ microprofile-config-api
+ compile
+
+
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+ true
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-datasource-hikaricp
+ runtime
+
+
+ io.smallrye
+ jandex
+ runtime
+ true
+
+
+ io.helidon.microprofile.server
+ helidon-microprofile-server
+ runtime
+
+
+ io.helidon.microprofile.config
+ helidon-microprofile-config
+ runtime
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
diff --git a/examples/integrations/cdi/datasource-hikaricp-mysql/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/TablesResource.java b/examples/integrations/cdi/datasource-hikaricp-mysql/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/TablesResource.java
new file mode 100644
index 000000000..4410a44cd
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp-mysql/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/TablesResource.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.datasource.hikaricp.jaxrs;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Objects;
+
+import javax.sql.DataSource;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * A JAX-RS resource class in {@linkplain ApplicationScoped
+ * application scope} rooted at {@code /tables}.
+ *
+ * @see #get()
+ */
+@Path("/tables")
+@ApplicationScoped
+public class TablesResource {
+
+ private final DataSource dataSource;
+
+ /**
+ * Creates a new {@link TablesResource}.
+ *
+ * @param dataSource the {@link DataSource} to use to acquire
+ * database table names; must not be {@code null}
+ *
+ * @exception NullPointerException if {@code dataSource} is {@code
+ * null}
+ */
+ @Inject
+ public TablesResource(@Named("example") final DataSource dataSource) {
+ super();
+ this.dataSource = Objects.requireNonNull(dataSource);
+ }
+
+ /**
+ * Returns a {@link Response} which, if successful, contains a
+ * newline-separated list of Oracle database table names.
+ *
+ * This method never returns {@code null}.
+ *
+ * @return a non-{@code null} {@link Response}
+ *
+ * @exception SQLException if a database error occurs
+ */
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public Response get() throws SQLException {
+ final StringBuilder sb = new StringBuilder();
+ try (Connection connection = this.dataSource.getConnection();
+ PreparedStatement ps =
+ connection.prepareStatement(" SELECT TABLE_NAME"
+ + " FROM INFORMATION_SCHEMA.TABLES "
+ + "ORDER BY TABLE_NAME ASC");
+ ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ sb.append(rs.getString(1)).append("\n");
+ }
+ }
+ final Response returnValue = Response.ok()
+ .entity(sb.toString())
+ .build();
+ return returnValue;
+ }
+
+}
diff --git a/examples/integrations/cdi/datasource-hikaricp-mysql/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/package-info.java b/examples/integrations/cdi/datasource-hikaricp-mysql/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/package-info.java
new file mode 100644
index 000000000..1f72fcff0
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp-mysql/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 JAX-RS-related classes and interfaces for this example
+ * project.
+ */
+package io.helidon.examples.integrations.datasource.hikaricp.jaxrs;
diff --git a/examples/integrations/cdi/datasource-hikaricp-mysql/src/main/resources/META-INF/beans.xml b/examples/integrations/cdi/datasource-hikaricp-mysql/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..a0938bff7
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp-mysql/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/integrations/cdi/datasource-hikaricp-mysql/src/main/resources/META-INF/microprofile-config.properties b/examples/integrations/cdi/datasource-hikaricp-mysql/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 000000000..2d984f717
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp-mysql/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,25 @@
+#
+# Copyright (c) 2018, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Default database properties.
+javax.sql.DataSource.example.dataSourceClassName=com.mysql.cj.jdbc.MysqlDataSource
+javax.sql.DataSource.example.dataSource.url = jdbc:mysql://localhost:3306
+javax.sql.DataSource.example.dataSource.user = root
+javax.sql.DataSource.example.dataSource.password = tiger
+
+# Microprofile server properties
+server.port=8080
+server.host=0.0.0.0
diff --git a/examples/integrations/cdi/datasource-hikaricp/.dockerignore b/examples/integrations/cdi/datasource-hikaricp/.dockerignore
new file mode 100644
index 000000000..2f7896d1d
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp/.dockerignore
@@ -0,0 +1 @@
+target/
diff --git a/examples/integrations/cdi/datasource-hikaricp/Dockerfile b/examples/integrations/cdi/datasource-hikaricp/Dockerfile
new file mode 100644
index 000000000..5caee647f
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp/Dockerfile
@@ -0,0 +1,53 @@
+#
+# Copyright (c) 2018, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# 1st stage, build the app
+FROM container-registry.oracle.com/java/jdk-no-fee-term:21 as build
+
+# Install maven
+WORKDIR /usr/share
+RUN set -x && \
+ curl -O https://archive.apache.org/dist/maven/maven-3/3.8.4/binaries/apache-maven-3.8.4-bin.tar.gz && \
+ tar -xvf apache-maven-*-bin.tar.gz && \
+ rm apache-maven-*-bin.tar.gz && \
+ mv apache-maven-* maven && \
+ ln -s /usr/share/maven/bin/mvn /bin/
+
+WORKDIR /helidon
+
+# Create a first layer to cache the "Maven World" in the local repository.
+# Incremental docker builds will always resume after that, unless you update
+# the pom
+ADD pom.xml .
+RUN mvn package -Dmaven.test.skip
+
+# Do the Maven build!
+# Incremental docker builds will resume here when you change sources
+ADD src src
+RUN mvn package -DskipTests
+RUN echo "done!"
+
+# 2nd stage, build the runtime image
+FROM container-registry.oracle.com/java/jdk-no-fee-term:21
+WORKDIR /helidon
+
+# Copy the binary built in the 1st stage
+COPY --from=build /helidon/target/helidon-examples-integrations-datasource-hikaricp.jar ./
+COPY --from=build /helidon/target/libs ./libs
+
+CMD [ "java", "-jar", "helidon-examples-integrations-datasource-hikaricp.jar" ]
+
+EXPOSE 8080
diff --git a/examples/integrations/cdi/datasource-hikaricp/README.md b/examples/integrations/cdi/datasource-hikaricp/README.md
new file mode 100644
index 000000000..cb72a0dee
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp/README.md
@@ -0,0 +1,85 @@
+# Hikari Connection Pool Integration Example
+
+## Overview
+
+This example shows a trivial Helidon MicroProfile application that
+uses the Hikari connection pool CDI integration. It also shows how to
+run the Oracle database in a Docker container and connect the
+application to it.
+
+## Prerequisites
+
+You'll need an Oracle account in order to log in to the Oracle
+Container Registry. The Oracle Container Registry is where the Docker
+image housing the Oracle database is located. To set up an Oracle
+account if you don't already have one, see
+[the Oracle account creation website](https://profile.oracle.com/myprofile/account/create-account.jspx).
+
+## Notes
+
+To log in to the Oracle Container Registry (which you will need to do
+in order to download Oracle database Docker images from it):
+
+```shell
+docker login -u username -p password container-registry.oracle.com
+```
+
+For more information on the Oracle Container Registry, please visit
+its [website](https://container-registry.oracle.com/).
+
+To run Oracle's `database/standard` Docker image in a Docker container
+named `oracle` that publishes ports 1521 and 5500 to
+the host while relying on the defaults for all other settings:
+
+```shell
+docker container run -d -it -p 1521:1521 -p 5500:5500 --shm-size=3g \
+ --name oracle \
+ container-registry.oracle.com/database/standard:latest
+```
+
+It will take about ten minutes before the database is ready.
+
+For more information on the Oracle database image used by this
+example, you can visit the relevant section of the
+ [Oracle Container Registry website](https://container-registry.oracle.com/).
+
+To ensure that the sample application is configured to talk to the
+Oracle database running in this Docker container, verify that the
+following lines (among others) are present in
+`src/main/resources/META-INF/microprofile-config.properties`:
+
+```properties
+javax.sql.DataSource.example.dataSourceClassName=oracle.jdbc.pool.OracleDataSource
+javax.sql.DataSource.example.dataSource.url = jdbc:oracle:thin:@localhost:1521:ORCL
+javax.sql.DataSource.example.dataSource.user = sys as sysoper
+javax.sql.DataSource.example.dataSource.password = Oracle
+```
+
+## Build and run
+
+With Docker:
+```shell
+docker build -t helidon-examples-integrations-datasource-hikaricp .
+docker run --rm -d \
+ --link oracle \
+ -e javax_sql_DataSource_example_dataSource_url="jdbc:oracle:thin:@oracle:1521:ORCL" \
+ --name helidon-examples-integrations-datasource-hikaricp \
+ -p 8080:8080 helidon-examples-integrations-datasource-hikaricp:latest
+```
+OR
+
+With Maven:
+```shell
+mvn package
+java -jar target/helidon-examples-integrations-datasource-hikaricp.jar
+```
+
+Try the endpoint:
+```shell
+curl http://localhost:8080/tables
+```
+
+Stop the docker containers:
+```shell
+docker stop oracle helidon-examples-integrations-datasource-hikaricp
+```
diff --git a/examples/integrations/cdi/datasource-hikaricp/pom.xml b/examples/integrations/cdi/datasource-hikaricp/pom.xml
new file mode 100644
index 000000000..6e0d78b80
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp/pom.xml
@@ -0,0 +1,103 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.integrations.cdi
+ helidon-examples-integrations-datasource-hikaricp
+ 1.0.0-SNAPSHOT
+ Helidon Examples CDI Extensions DataSource/HikariCP
+
+
+
+
+ jakarta.enterprise
+ jakarta.enterprise.cdi-api
+ compile
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+ compile
+
+
+ org.eclipse.microprofile.config
+ microprofile-config-api
+ compile
+
+
+
+
+ io.helidon.integrations.db
+ ojdbc
+ runtime
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-datasource-hikaricp
+ runtime
+
+
+ io.smallrye
+ jandex
+ runtime
+ true
+
+
+ io.helidon.microprofile.server
+ helidon-microprofile-server
+ runtime
+
+
+ io.helidon.microprofile.config
+ helidon-microprofile-config
+ runtime
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
diff --git a/examples/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/TablesResource.java b/examples/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/TablesResource.java
new file mode 100644
index 000000000..52ff77118
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/TablesResource.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2018, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.datasource.hikaricp.jaxrs;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Objects;
+
+import javax.sql.DataSource;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * A JAX-RS resource class in {@linkplain ApplicationScoped
+ * application scope} rooted at {@code /tables}.
+ *
+ * @see #get()
+ */
+@Path("/tables")
+@ApplicationScoped
+public class TablesResource {
+
+ private final DataSource dataSource;
+
+ /**
+ * Creates a new {@link TablesResource}.
+ *
+ * @param dataSource the {@link DataSource} to use to acquire
+ * database table names; must not be {@code null}
+ *
+ * @exception NullPointerException if {@code dataSource} is {@code
+ * null}
+ */
+ @Inject
+ public TablesResource(@Named("example") final DataSource dataSource) {
+ super();
+ this.dataSource = Objects.requireNonNull(dataSource);
+ }
+
+ /**
+ * Returns a {@link Response} which, if successful, contains a
+ * newline-separated list of Oracle database table names.
+ *
+ * This method never returns {@code null}.
+ *
+ * @return a non-{@code null} {@link Response}
+ *
+ * @exception SQLException if a database error occurs
+ */
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public Response get() throws SQLException {
+ final StringBuilder sb = new StringBuilder();
+ try (Connection connection = this.dataSource.getConnection();
+ PreparedStatement ps =
+ connection.prepareStatement(" SELECT TABLE_NAME"
+ + " FROM ALL_TABLES "
+ + "ORDER BY TABLE_NAME ASC");
+ ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ sb.append(rs.getString(1)).append("\n");
+ }
+ }
+ final Response returnValue = Response.ok()
+ .entity(sb.toString())
+ .build();
+ return returnValue;
+ }
+
+}
diff --git a/examples/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/package-info.java b/examples/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/package-info.java
new file mode 100644
index 000000000..7dbf84052
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/examples/integrations/datasource/hikaricp/jaxrs/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2018, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 JAX-RS-related classes and interfaces for this example
+ * project.
+ */
+package io.helidon.examples.integrations.datasource.hikaricp.jaxrs;
diff --git a/examples/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/beans.xml b/examples/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..ddb8316e3
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/microprofile-config.properties b/examples/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 000000000..0ce3463ec
--- /dev/null
+++ b/examples/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,29 @@
+#
+# Copyright (c) 2018, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Default database properties.
+# See
+# https://container-registry.oracle.com/pls/apex/f?p=113:4:102624334361221::NO:::
+# and
+# https://technology.amis.nl/2017/11/18/run-oracle-database-in-docker-using-prebaked-image-from-oracle-container-registry-a-two-minute-guide/
+javax.sql.DataSource.example.dataSourceClassName=oracle.jdbc.pool.OracleDataSource
+javax.sql.DataSource.example.dataSource.url = jdbc:oracle:thin:@localhost:1521:ORCL
+javax.sql.DataSource.example.dataSource.user = sys as sysoper
+javax.sql.DataSource.example.dataSource.password = Oracle
+
+# Microprofile server properties
+server.port=8080
+server.host=0.0.0.0
diff --git a/examples/integrations/cdi/jpa/README.md b/examples/integrations/cdi/jpa/README.md
new file mode 100644
index 000000000..5a202ed5f
--- /dev/null
+++ b/examples/integrations/cdi/jpa/README.md
@@ -0,0 +1,13 @@
+# JPA Integration Example
+
+With Java:
+```shell
+mvn package
+java -jar target/helidon-integrations-examples-jpa.jar
+```
+
+Try the endpoint:
+```shell
+curl -X POST -H "Content-Type: text/plain" http://localhost:8080/foo -d 'bar'
+curl http://localhost:8080/foo
+```
diff --git a/examples/integrations/cdi/jpa/pom.xml b/examples/integrations/cdi/jpa/pom.xml
new file mode 100644
index 000000000..644c1bac9
--- /dev/null
+++ b/examples/integrations/cdi/jpa/pom.xml
@@ -0,0 +1,187 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.integrations.cdi
+ helidon-integrations-examples-jpa
+ 1.0.0-SNAPSHOT
+ Helidon Examples CDI Extensions JPA
+
+
+
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+ compile
+
+
+ jakarta.enterprise
+ jakarta.enterprise.cdi-api
+ compile
+
+
+ jakarta.inject
+ jakarta.inject-api
+ compile
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+ compile
+
+
+ jakarta.persistence
+ jakarta.persistence-api
+ compile
+
+
+ jakarta.transaction
+ jakarta.transaction-api
+ compile
+
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-eclipselink
+ runtime
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-jta-weld
+ runtime
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-datasource-hikaricp
+ runtime
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-jpa
+ runtime
+
+
+ io.smallrye
+ jandex
+ runtime
+ true
+
+
+ io.helidon.microprofile.server
+ helidon-microprofile-server
+ runtime
+
+
+ io.helidon.microprofile.config
+ helidon-microprofile-config
+ runtime
+
+
+ org.eclipse.microprofile.config
+ microprofile-config-api
+ runtime
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.hibernate.orm
+ hibernate-jpamodelgen
+ ${version.lib.hibernate}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ weave
+ process-classes
+
+ java
+
+
+ org.eclipse.persistence.tools.weaving.jpa.StaticWeave
+
+ -loglevel
+ INFO
+ ${project.build.outputDirectory}
+ ${project.build.outputDirectory}
+
+
+
+
+
+
+
+
diff --git a/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/Greeting.java b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/Greeting.java
new file mode 100644
index 000000000..4ca2788e9
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/Greeting.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.cdi.jpa;
+
+import java.util.Objects;
+
+import jakarta.persistence.Access;
+import jakarta.persistence.AccessType;
+import jakarta.persistence.Basic;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+
+/**
+ * A contrived representation for example purposes only of a two-part
+ * greeting as might be stored in a database.
+ */
+@Access(AccessType.FIELD)
+@Entity(name = "Greeting")
+@Table(name = "GREETING")
+public class Greeting {
+
+ @Id
+ @Column(name = "FIRSTPART", insertable = true, nullable = false, updatable = false)
+ private String firstPart;
+
+ @Basic(optional = false)
+ @Column(name = "SECONDPART", insertable = true, nullable = false, updatable = true)
+ private String secondPart;
+
+ /**
+ * Creates a new {@link Greeting}; required by the JPA
+ * specification and for no other purpose.
+ *
+ * @deprecated Please use the {@link #Greeting(String,
+ * String)} constructor instead.
+ *
+ * @see #Greeting(String, String)
+ */
+ @Deprecated
+ protected Greeting() {
+ super();
+ }
+
+ /**
+ * Creates a new {@link Greeting}.
+ *
+ * @param firstPart the first part of the greeting; must not be
+ * {@code null}
+ *
+ * @param secondPart the second part of the greeting; must not be
+ * {@code null}
+ *
+ * @exception NullPointerException if {@code firstPart} or {@code
+ * secondPart} is {@code null}
+ */
+ public Greeting(final String firstPart, final String secondPart) {
+ super();
+ this.firstPart = Objects.requireNonNull(firstPart);
+ this.secondPart = Objects.requireNonNull(secondPart);
+ }
+
+ /**
+ * Sets the second part of this greeting.
+ *
+ * @param secondPart the second part of this greeting; must not be
+ * {@code null}
+ *
+ * @exception NullPointerException if {@code secondPart} is {@code
+ * null}
+ */
+ public void setSecondPart(final String secondPart) {
+ this.secondPart = Objects.requireNonNull(secondPart);
+ }
+
+ /**
+ * Returns a {@link String} representation of the second part of
+ * this {@link Greeting}.
+ *
+ * This method never returns {@code null}.
+ *
+ * @return a non-{@code null} {@link String} representation of the
+ * second part of this {@link Greeting}
+ */
+ @Override
+ public String toString() {
+ return this.secondPart;
+ }
+
+}
diff --git a/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/HelloWorldApplication.java b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/HelloWorldApplication.java
new file mode 100644
index 000000000..0b536de4b
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/HelloWorldApplication.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.cdi.jpa;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.core.Application;
+
+/**
+ * An example {@link Application} demonstrating the modular
+ * integration of JPA and JTA with Helidon MicroProfile.
+ */
+@ApplicationScoped
+public class HelloWorldApplication extends Application {
+
+ private final Set> classes;
+
+ /**
+ * Creates a new {@link HelloWorldApplication}.
+ */
+ public HelloWorldApplication() {
+ super();
+ final Set> classes = new HashSet<>();
+ classes.add(HelloWorldResource.class);
+ classes.add(JPAExceptionMapper.class);
+ this.classes = Collections.unmodifiableSet(classes);
+ }
+
+ /**
+ * Returns a non-{@code null} {@link Set} of {@link Class}es that
+ * comprise this JAX-RS application.
+ *
+ * @return a non-{@code null}, {@linkplain
+ * Collections#unmodifiableSet(Set) unmodifiable Set
}
+ *
+ * @see HelloWorldResource
+ */
+ @Override
+ public Set> getClasses() {
+ return this.classes;
+ }
+
+}
diff --git a/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/HelloWorldResource.java b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/HelloWorldResource.java
new file mode 100644
index 000000000..efd85913a
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/HelloWorldResource.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.cdi.jpa;
+
+import java.net.URI;
+import java.util.Objects;
+
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import jakarta.persistence.PersistenceException;
+import jakarta.transaction.Status;
+import jakarta.transaction.SystemException;
+import jakarta.transaction.Transaction;
+import jakarta.transaction.Transactional;
+import jakarta.transaction.Transactional.TxType;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * A JAX-RS root resource class that manipulates greetings in a
+ * database.
+ *
+ * @see #get(String)
+ *
+ * @see #post(String, String)
+ */
+@Path("")
+@RequestScoped
+public class HelloWorldResource {
+
+ /**
+ * The {@link EntityManager} used by this class.
+ *
+ * Note that it behaves as though there is a transaction manager
+ * in effect, because there is.
+ */
+ @PersistenceContext(unitName = "test")
+ private EntityManager entityManager;
+
+ /**
+ * A {@link Transaction} that is guaranteed to be non-{@code null}
+ * only when a transactional method is executing.
+ *
+ * @see #post(String, String)
+ */
+ @Inject
+ private Transaction transaction;
+
+ /**
+ * Creates a new {@link HelloWorldResource}.
+ */
+ public HelloWorldResource() {
+ super();
+ }
+
+ /**
+ * Returns a {@link Response} with a status of {@code 404} when
+ * invoked.
+ *
+ * @return a non-{@code null} {@link Response}
+ */
+ @GET
+ @Path("favicon.ico")
+ public Response getFavicon() {
+ return Response.status(404).build();
+ }
+
+ /**
+ * When handed a {@link String} like, say, "{@code hello}", responds
+ * with the second part of the composite greeting as found via an
+ * {@link EntityManager}.
+ *
+ * @param firstPart the first part of the greeting; must not be
+ * {@code null}
+ *
+ * @return the second part of the greeting; never {@code null}
+ *
+ * @exception NullPointerException if {@code firstPart} was {@code
+ * null}
+ *
+ * @exception PersistenceException if the {@link EntityManager}
+ * encountered an error
+ */
+ @GET
+ @Path("{firstPart}")
+ @Produces(MediaType.TEXT_PLAIN)
+ public String get(@PathParam("firstPart") final String firstPart) {
+ Objects.requireNonNull(firstPart);
+ assert this.entityManager != null;
+ final Greeting greeting = this.entityManager.find(Greeting.class, firstPart);
+ assert greeting != null;
+ return greeting.toString();
+ }
+
+ /**
+ * When handed two parts of a greeting, like, say, "{@code hello}"
+ * and "{@code world}", stores a new {@link Greeting} entity in the
+ * database appropriately.
+ *
+ * @param firstPart the first part of the greeting; must not be
+ * {@code null}
+ *
+ * @param secondPart the second part of the greeting; must not be
+ * {@code null}
+ *
+ * @return the {@link String} representation of the resulting {@link
+ * Greeting}'s identifier; never {@code null}
+ *
+ * @exception NullPointerException if {@code firstPart} or {@code
+ * secondPart} was {@code null}
+ *
+ * @exception PersistenceException if the {@link EntityManager}
+ * encountered an error
+ *
+ * @exception SystemException if something went wrong with the
+ * transaction
+ */
+ @POST
+ @Path("{firstPart}")
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Produces(MediaType.TEXT_PLAIN)
+ @Transactional(TxType.REQUIRED)
+ public Response post(@PathParam("firstPart") final String firstPart,
+ final String secondPart)
+ throws SystemException {
+ Objects.requireNonNull(firstPart);
+ Objects.requireNonNull(secondPart);
+ assert this.transaction != null;
+ assert this.transaction.getStatus() == Status.STATUS_ACTIVE;
+ assert this.entityManager != null;
+ assert this.entityManager.isJoinedToTransaction();
+ Greeting greeting = this.entityManager.find(Greeting.class, firstPart);
+ final boolean created;
+ if (greeting == null) {
+ greeting = new Greeting(firstPart, secondPart);
+ this.entityManager.persist(greeting);
+ created = true;
+ } else {
+ greeting.setSecondPart(secondPart);
+ created = false;
+ }
+ assert this.entityManager.contains(greeting);
+ if (created) {
+ return Response.created(URI.create(firstPart)).build();
+ } else {
+ return Response.ok(firstPart).build();
+ }
+ }
+
+}
diff --git a/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/JPAExceptionMapper.java b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/JPAExceptionMapper.java
new file mode 100644
index 000000000..d302a29e5
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/JPAExceptionMapper.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.cdi.jpa;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.persistence.EntityNotFoundException;
+import jakarta.persistence.NoResultException;
+import jakarta.persistence.PersistenceException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+/**
+ * An {@link ExceptionMapper} that handles {@link
+ * PersistenceException}s.
+ *
+ * @see ExceptionMapper
+ */
+@ApplicationScoped
+@Provider
+public class JPAExceptionMapper implements ExceptionMapper {
+
+ /**
+ * Creates a new {@link JPAExceptionMapper}.
+ */
+ public JPAExceptionMapper() {
+ super();
+ }
+
+ /**
+ * Returns an appropriate non-{@code null} {@link Response} for the
+ * supplied {@link PersistenceException}.
+ *
+ * @param persistenceException the {@link PersistenceException} that
+ * caused this {@link JPAExceptionMapper} to be invoked; may be
+ * {@code null}
+ *
+ * @return a non-{@code null} {@link Response} representing the
+ * error
+ */
+ @Override
+ public Response toResponse(final PersistenceException persistenceException) {
+ final Response returnValue;
+ if (persistenceException instanceof NoResultException
+ || persistenceException instanceof EntityNotFoundException) {
+ returnValue = Response.status(404).build();
+ } else {
+ returnValue = null;
+ throw persistenceException;
+ }
+ return returnValue;
+ }
+
+}
diff --git a/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/package-info.java b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/package-info.java
new file mode 100644
index 000000000..7c80b84a3
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 demonstrating the usage of JPA and
+ * JTA integration within Helidon MicroProfile.
+ */
+package io.helidon.examples.integrations.cdi.jpa;
diff --git a/examples/integrations/cdi/jpa/src/main/resources/META-INF/beans.xml b/examples/integrations/cdi/jpa/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..a0938bff7
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/integrations/cdi/jpa/src/main/resources/META-INF/microprofile-config.properties b/examples/integrations/cdi/jpa/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 000000000..4e2866d89
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,23 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+javax.sql.DataSource.test.dataSourceClassName=org.h2.jdbcx.JdbcDataSource
+javax.sql.DataSource.test.dataSource.url=jdbc:h2:mem:test;INIT=CREATE TABLE IF NOT EXISTS GREETING (FIRSTPART VARCHAR NOT NULL, SECONDPART VARCHAR NOT NULL, PRIMARY KEY (FIRSTPART))\\;MERGE INTO GREETING (FIRSTPART, SECONDPART) VALUES ('hello', 'world')
+javax.sql.DataSource.test.dataSource.user=sa
+javax.sql.DataSource.test.dataSource.password=${EMPTY}
+
+# Microprofile server properties
+server.port=8080
+server.host=0.0.0.0
diff --git a/examples/integrations/cdi/jpa/src/main/resources/META-INF/persistence.xml b/examples/integrations/cdi/jpa/src/main/resources/META-INF/persistence.xml
new file mode 100644
index 000000000..42fae4fd7
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,52 @@
+
+
+
+
+ test
+ io.helidon.examples.integrations.cdi.jpa.Greeting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/integrations/cdi/pokemons/README.md b/examples/integrations/cdi/pokemons/README.md
new file mode 100644
index 000000000..132dd40af
--- /dev/null
+++ b/examples/integrations/cdi/pokemons/README.md
@@ -0,0 +1,25 @@
+# JPA Pokemons Example
+
+With Java:
+```shell
+mvn package
+java -jar target/helidon-integrations-examples-pokemons.jar
+```
+
+## Exercise the application
+
+```shell
+curl -X GET http://localhost:8080/pokemon
+#Output: [{"id":1,"type":12,"name":"Bulbasaur"}, ...]
+```
+```shell
+curl -X GET http://localhost:8080/type
+#Output: [{"id":1,"name":"Normal"}, ...]
+```
+```shell
+curl -H "Content-Type: application/json" --request POST --data '{"id":100, "type":1, "name":"Test"}' http://localhost:8080/pokemon
+```
+
+---
+
+Pokémon, and Pokémon character names are trademarks of Nintendo.
diff --git a/examples/integrations/cdi/pokemons/pom.xml b/examples/integrations/cdi/pokemons/pom.xml
new file mode 100644
index 000000000..0aed392b6
--- /dev/null
+++ b/examples/integrations/cdi/pokemons/pom.xml
@@ -0,0 +1,185 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.integrations.cdi
+ helidon-integrations-examples-pokemons
+ 1.0.0-SNAPSHOT
+ Helidon Examples CDI Extensions Pokemons JPA
+
+
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+
+
+ jakarta.enterprise
+ jakarta.enterprise.cdi-api
+
+
+ jakarta.inject
+ jakarta.inject-api
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+
+
+ jakarta.json.bind
+ jakarta.json.bind-api
+
+
+ jakarta.persistence
+ jakarta.persistence-api
+
+
+ jakarta.transaction
+ jakarta.transaction-api
+
+
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-hibernate
+ runtime
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-jta-weld
+ runtime
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-datasource-hikaricp
+ runtime
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-jpa
+ runtime
+
+
+ io.smallrye
+ jandex
+ runtime
+ true
+
+
+ io.helidon.microprofile.server
+ helidon-microprofile-server
+ runtime
+
+
+ io.helidon.microprofile.metrics
+ helidon-microprofile-metrics
+ runtime
+
+
+ org.glassfish.jersey.media
+ jersey-media-json-binding
+ runtime
+
+
+ io.helidon.microprofile.config
+ helidon-microprofile-config
+ runtime
+
+
+ org.eclipse.microprofile.config
+ microprofile-config-api
+ runtime
+
+
+ org.hibernate.validator
+ hibernate-validator-cdi
+ runtime
+
+
+ org.glassfish
+ jakarta.el
+ runtime
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+ org.hibernate.orm.tooling
+ hibernate-enhance-maven-plugin
+
+
+
+ true
+ true
+ true
+
+
+ enhance
+
+
+
+
+
+
+
diff --git a/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/Pokemon.java b/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/Pokemon.java
new file mode 100644
index 000000000..fb1cd99e2
--- /dev/null
+++ b/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/Pokemon.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.cdi.pokemon;
+
+import jakarta.json.bind.annotation.JsonbTransient;
+import jakarta.persistence.Access;
+import jakarta.persistence.AccessType;
+import jakarta.persistence.Basic;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.NamedQueries;
+import jakarta.persistence.NamedQuery;
+import jakarta.persistence.Table;
+import jakarta.persistence.Transient;
+
+/**
+ * A Pokemon entity class. A Pokemon is represented as a triple of an
+ * ID, a name and a type.
+ */
+@Entity(name = "Pokemon")
+@Table(name = "POKEMON")
+@Access(AccessType.PROPERTY)
+@NamedQueries({
+ @NamedQuery(name = "getPokemons",
+ query = "SELECT p FROM Pokemon p"),
+ @NamedQuery(name = "getPokemonByName",
+ query = "SELECT p FROM Pokemon p WHERE p.name = :name")
+})
+public class Pokemon {
+
+ private int id;
+
+ private String name;
+
+ @JsonbTransient
+ private PokemonType pokemonType;
+
+ private int type;
+
+ /**
+ * Creates a new pokemon.
+ */
+ public Pokemon() {
+ }
+
+ @Id
+ @Column(name = "ID", nullable = false, updatable = false)
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ @Basic(optional = false)
+ @Column(name = "NAME", nullable = false)
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns pokemon's type.
+ *
+ * @return Pokemon's type.
+ */
+ @ManyToOne
+ public PokemonType getPokemonType() {
+ return pokemonType;
+ }
+
+ /**
+ * Sets pokemon's type.
+ *
+ * @param pokemonType Pokemon's type.
+ */
+ public void setPokemonType(PokemonType pokemonType) {
+ this.pokemonType = pokemonType;
+ this.type = pokemonType.getId();
+ }
+
+ @Transient
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+}
diff --git a/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/PokemonResource.java b/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/PokemonResource.java
new file mode 100644
index 000000000..918567c37
--- /dev/null
+++ b/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/PokemonResource.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.cdi.pokemon;
+
+import java.util.List;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import jakarta.persistence.TypedQuery;
+import jakarta.transaction.Transactional;
+import jakarta.ws.rs.BadRequestException;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+/**
+ * This class implements REST endpoints to interact with Pokemons. The following
+ * operations are supported:
+ *
+ * GET /pokemon: Retrieve list of all pokemons
+ * GET /pokemon/{id}: Retrieve single pokemon by ID
+ * GET /pokemon/name/{name}: Retrieve single pokemon by name
+ * DELETE /pokemon/{id}: Delete a pokemon by ID
+ * POST /pokemon: Create a new pokemon
+ */
+@Path("pokemon")
+public class PokemonResource {
+
+ @PersistenceContext(unitName = "test")
+ private EntityManager entityManager;
+
+ /**
+ * Retrieves list of all pokemons.
+ *
+ * @return List of pokemons.
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public List getPokemons() {
+ return entityManager.createNamedQuery("getPokemons", Pokemon.class).getResultList();
+ }
+
+ /**
+ * Retrieves single pokemon by ID.
+ *
+ * @param id The ID.
+ * @return A pokemon that matches the ID.
+ * @throws NotFoundException If no pokemon found for the ID.
+ */
+ @GET
+ @Path("{id}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Pokemon getPokemonById(@PathParam("id") String id) {
+ try {
+ return entityManager.find(Pokemon.class, Integer.valueOf(id));
+ } catch (IllegalArgumentException e) {
+ throw new NotFoundException("Unable to find pokemon with ID " + id);
+ }
+ }
+
+ /**
+ * Deletes a single pokemon by ID.
+ *
+ * @param id The ID.
+ */
+ @DELETE
+ @Path("{id}")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Transactional(Transactional.TxType.REQUIRED)
+ public void deletePokemon(@PathParam("id") String id) {
+ Pokemon pokemon = getPokemonById(id);
+ entityManager.remove(pokemon);
+ }
+
+ /**
+ * Retrieves a pokemon by name.
+ *
+ * @param name The name.
+ * @return A pokemon that matches the name.
+ * @throws NotFoundException If no pokemon found for the name.
+ */
+ @GET
+ @Path("name/{name}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Pokemon getPokemonByName(@PathParam("name") String name) {
+ TypedQuery query = entityManager.createNamedQuery("getPokemonByName", Pokemon.class);
+ List list = query.setParameter("name", name).getResultList();
+ if (list.isEmpty()) {
+ throw new NotFoundException("Unable to find pokemon with name " + name);
+ }
+ return list.get(0);
+ }
+
+ /**
+ * Creates a new pokemon.
+ *
+ * @param pokemon New pokemon.
+ * @throws BadRequestException If a problem was found.
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Transactional(Transactional.TxType.REQUIRED)
+ public void createPokemon(Pokemon pokemon) {
+ try {
+ PokemonType pokemonType = entityManager.createNamedQuery("getPokemonTypeById", PokemonType.class)
+ .setParameter("id", pokemon.getType()).getSingleResult();
+ pokemon.setPokemonType(pokemonType);
+ entityManager.persist(pokemon);
+ } catch (Exception e) {
+ throw new BadRequestException("Unable to create pokemon with ID " + pokemon.getId());
+ }
+ }
+}
+
diff --git a/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/PokemonType.java b/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/PokemonType.java
new file mode 100644
index 000000000..7e19af87e
--- /dev/null
+++ b/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/PokemonType.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.cdi.pokemon;
+
+import jakarta.persistence.Access;
+import jakarta.persistence.AccessType;
+import jakarta.persistence.Basic;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.NamedQueries;
+import jakarta.persistence.NamedQuery;
+import jakarta.persistence.Table;
+
+/**
+ * A Pokemon Type entity. A type is represented by an ID and a name.
+ */
+@Entity(name = "PokemonType")
+@Table(name = "POKEMONTYPE")
+@Access(AccessType.FIELD)
+@NamedQueries({
+ @NamedQuery(name = "getPokemonTypes",
+ query = "SELECT t FROM PokemonType t"),
+ @NamedQuery(name = "getPokemonTypeById",
+ query = "SELECT t FROM PokemonType t WHERE t.id = :id")
+})
+public class PokemonType {
+
+ @Id
+ @Column(name = "ID", nullable = false, updatable = false)
+ private int id;
+
+ @Basic(optional = false)
+ @Column(name = "NAME")
+ private String name;
+
+ /**
+ * Creates a new type.
+ */
+ public PokemonType() {
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/PokemonTypeResource.java b/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/PokemonTypeResource.java
new file mode 100644
index 000000000..70bc9c981
--- /dev/null
+++ b/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/PokemonTypeResource.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.cdi.pokemon;
+
+import java.util.List;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+/**
+ * This class implements a REST endpoint to retrieve Pokemon types.
+ *
+ * GET /type: Retrieve list of all pokemon types
+ */
+@Path("type")
+public class PokemonTypeResource {
+
+ @PersistenceContext(unitName = "test")
+ private EntityManager entityManager;
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public List getPokemonTypes() {
+ return entityManager.createNamedQuery("getPokemonTypes", PokemonType.class).getResultList();
+ }
+}
diff --git a/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/package-info.java b/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/package-info.java
new file mode 100644
index 000000000..85a2ffde6
--- /dev/null
+++ b/examples/integrations/cdi/pokemons/src/main/java/io/helidon/examples/integrations/cdi/pokemon/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Pokemon JPA integration sample.
+ */
+package io.helidon.examples.integrations.cdi.pokemon;
diff --git a/examples/integrations/cdi/pokemons/src/main/resources/META-INF/beans.xml b/examples/integrations/cdi/pokemons/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..676e09a2d
--- /dev/null
+++ b/examples/integrations/cdi/pokemons/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/integrations/cdi/pokemons/src/main/resources/META-INF/init_script.sql b/examples/integrations/cdi/pokemons/src/main/resources/META-INF/init_script.sql
new file mode 100644
index 000000000..8d3d00c78
--- /dev/null
+++ b/examples/integrations/cdi/pokemons/src/main/resources/META-INF/init_script.sql
@@ -0,0 +1,25 @@
+INSERT INTO POKEMONTYPE VALUES (1, 'Normal');
+INSERT INTO POKEMONTYPE VALUES (2, 'Fighting');
+INSERT INTO POKEMONTYPE VALUES (3, 'Flying');
+INSERT INTO POKEMONTYPE VALUES (4, 'Poison');
+INSERT INTO POKEMONTYPE VALUES (5, 'Ground');
+INSERT INTO POKEMONTYPE VALUES (6, 'Rock');
+INSERT INTO POKEMONTYPE VALUES (7, 'Bug');
+INSERT INTO POKEMONTYPE VALUES (8, 'Ghost');
+INSERT INTO POKEMONTYPE VALUES (9, 'Steel');
+INSERT INTO POKEMONTYPE VALUES (10, 'Fire');
+INSERT INTO POKEMONTYPE VALUES (11, 'Water');
+INSERT INTO POKEMONTYPE VALUES (12, 'Grass');
+INSERT INTO POKEMONTYPE VALUES (13, 'Electric');
+INSERT INTO POKEMONTYPE VALUES (14, 'Psychic');
+INSERT INTO POKEMONTYPE VALUES (15, 'Ice');
+INSERT INTO POKEMONTYPE VALUES (16, 'Dragon');
+INSERT INTO POKEMONTYPE VALUES (17, 'Dark');
+INSERT INTO POKEMONTYPE VALUES (18, 'Fairy');
+
+INSERT INTO POKEMON VALUES (1, 'Bulbasaur', 12);
+INSERT INTO POKEMON VALUES (2, 'Charmander', 10);
+INSERT INTO POKEMON VALUES (3, 'Squirtle', 11);
+INSERT INTO POKEMON VALUES (4, 'Caterpie', 7);
+INSERT INTO POKEMON VALUES (5, 'Weedle', 7);
+INSERT INTO POKEMON VALUES (6, 'Pidgey', 3);
diff --git a/examples/integrations/cdi/pokemons/src/main/resources/META-INF/microprofile-config.properties b/examples/integrations/cdi/pokemons/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 000000000..4559b00f7
--- /dev/null
+++ b/examples/integrations/cdi/pokemons/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+javax.sql.DataSource.test.dataSourceClassName=org.h2.jdbcx.JdbcDataSource
+#javax.sql.DataSource.test.dataSource.url=jdbc:h2:tcp://localhost:1521/test;IFEXISTS=FALSE
+javax.sql.DataSource.test.dataSource.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
+javax.sql.DataSource.test.dataSource.user=sa
+javax.sql.DataSource.test.dataSource.password=${EMPTY}
+
+# Microprofile server properties
+server.port=8080
+server.host=0.0.0.0
diff --git a/examples/integrations/cdi/pokemons/src/main/resources/META-INF/persistence.xml b/examples/integrations/cdi/pokemons/src/main/resources/META-INF/persistence.xml
new file mode 100644
index 000000000..cc9103e8a
--- /dev/null
+++ b/examples/integrations/cdi/pokemons/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,35 @@
+
+
+
+
+ test
+ io.helidon.examples.integrations.cdi.pokemon.Pokemon
+ io.helidon.examples.integrations.cdi.pokemon.PokemonType
+
+
+
+
+
+
+
+
diff --git a/examples/integrations/cdi/pokemons/src/test/java/io/helidon/examples/integrations/cdi/pokemon/MainTest.java b/examples/integrations/cdi/pokemons/src/test/java/io/helidon/examples/integrations/cdi/pokemon/MainTest.java
new file mode 100644
index 000000000..ec72ed297
--- /dev/null
+++ b/examples/integrations/cdi/pokemons/src/test/java/io/helidon/examples/integrations/cdi/pokemon/MainTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.cdi.pokemon;
+
+import io.helidon.microprofile.server.Server;
+
+import jakarta.enterprise.inject.se.SeContainer;
+import jakarta.enterprise.inject.spi.CDI;
+import jakarta.json.JsonArray;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class MainTest {
+
+ private static Server server;
+ private static Client client;
+
+ @BeforeAll
+ public static void startTheServer() {
+ client = ClientBuilder.newClient();
+ server = Server.create().start();
+ }
+
+ @AfterAll
+ static void destroyClass() {
+ CDI current = CDI.current();
+ ((SeContainer) current).close();
+ }
+
+ @Test
+ void testPokemonTypes() {
+ JsonArray types = client.target(getConnectionString("/type"))
+ .request()
+ .get(JsonArray.class);
+ assertThat(types.size(), is(18));
+ }
+
+ @Test
+ void testPokemon() {
+ assertThat(getPokemonCount(), is(6));
+
+ Pokemon pokemon = client.target(getConnectionString("/pokemon/1"))
+ .request()
+ .get(Pokemon.class);
+ assertThat(pokemon.getName(), is("Bulbasaur"));
+
+ pokemon = client.target(getConnectionString("/pokemon/name/Charmander"))
+ .request()
+ .get(Pokemon.class);
+ assertThat(pokemon.getType(), is(10));
+
+ try (Response response = client.target(getConnectionString("/pokemon/1"))
+ .request()
+ .get()) {
+ assertThat(response.getStatus(), is(200));
+ }
+
+ Pokemon test = new Pokemon();
+ test.setType(1);
+ test.setId(100);
+ test.setName("Test");
+ try (Response response = client.target(getConnectionString("/pokemon"))
+ .request()
+ .post(Entity.entity(test, MediaType.APPLICATION_JSON))) {
+ assertThat(response.getStatus(), is(204));
+ assertThat(getPokemonCount(), is(7));
+ }
+
+ try (Response response = client.target(getConnectionString("/pokemon/100"))
+ .request()
+ .delete()) {
+ assertThat(response.getStatus(), is(204));
+ assertThat(getPokemonCount(), is(6));
+ }
+ }
+
+ private int getPokemonCount() {
+ JsonArray pokemons = client.target(getConnectionString("/pokemon"))
+ .request()
+ .get(JsonArray.class);
+ return pokemons.size();
+ }
+
+ private String getConnectionString(String path) {
+ return "http://localhost:" + server.port() + path;
+ }
+}
diff --git a/examples/integrations/cdi/pom.xml b/examples/integrations/cdi/pom.xml
new file mode 100644
index 000000000..98f65ed15
--- /dev/null
+++ b/examples/integrations/cdi/pom.xml
@@ -0,0 +1,42 @@
+
+
+
+ 4.0.0
+
+ io.helidon.examples.integrations
+ helidon-examples-integrations-project
+ 1.0.0-SNAPSHOT
+
+ io.helidon.examples.integrations.cdi
+ helidon-examples-integrations-cdi-project
+ 1.0.0-SNAPSHOT
+ pom
+ Helidon Examples CDI Extensions
+
+
+ datasource-hikaricp
+ datasource-hikaricp-h2
+ datasource-hikaricp-mysql
+ jpa
+ pokemons
+
+
diff --git a/examples/integrations/micrometer/mp/README.md b/examples/integrations/micrometer/mp/README.md
new file mode 100644
index 000000000..57032897c
--- /dev/null
+++ b/examples/integrations/micrometer/mp/README.md
@@ -0,0 +1,80 @@
+# Helidon MP Micrometer Example
+
+This example shows a simple greeting application, similar to the one from the Helidon MP
+QuickStart, but enhanced with Helidon MP Micrometer support to
+* time all accesses to the two `GET` endpoints, and
+
+* count the accesses to the `GET` endpoint which returns a personalized
+ greeting.
+
+The example is similar to the one from the Helidon MP QuickStart with these differences:
+* The `pom.xml` file contains this additional dependency on the Helidon Micrometer integration
+ module:
+```xml
+
+ io.helidon.integrations
+ helidon-integrations-micrometer
+
+```
+* The `GreetingService` includes additional annotations:
+ * `@Timed` on the two `GET` methods.
+ * `@Counted` on the `GET` method that returns a personalized greeting.
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-integrations-micrometer-mp.jar
+```
+
+## Using the app endpoints as with the "classic" greeting app
+
+These normal greeting app endpoints work just as in the original greeting app:
+
+```shell
+curl -X GET http://localhost:8080/greet
+#Output: {"message":"Hello World!"}
+```
+
+```shell
+curl -X GET http://localhost:8080/greet/Joe
+#Output: {"message":"Hello Joe!"}
+```
+
+```shell
+curl -X PUT -H "Content-Type: application/json" -d '{"message" : "Hola"}' http://localhost:8080/greet/greeting
+
+curl -X GET http://localhost:8080/greet/Jose
+#Output: {"message":"Hola Jose!"}
+```
+
+## Using Micrometer
+
+Access the `/micrometer` endpoint which reports the newly-added timer and counter.
+
+```shell
+curl http://localhost:8080/micrometer
+```
+Because the `@Timer` annotation specifies a histogram,
+the actual timer output includes a lengthy histogram (only part of which is shown below).
+Your output might show the `personalizedGets` output before the `allGets` output,
+rather than after as shown here.
+
+```shell
+curl http://localhost:8080/micrometer
+```
+```listing
+# HELP allGets_seconds_max Tracks all GET operations
+# TYPE allGets_seconds_max gauge
+allGets_seconds_max 0.004840005
+# HELP allGets_seconds Tracks all GET operations
+# TYPE allGets_seconds histogram
+allGets_seconds_bucket{le="0.001",} 2.0
+allGets_seconds_bucket{le="30.0",} 3.0
+allGets_seconds_bucket{le="+Inf",} 3.0
+allGets_seconds_count 3.0
+allGets_seconds_sum 0.005098119
+# HELP personalizedGets_total Counts personalized GET operations
+# TYPE personalizedGets_total counter
+personalizedGets_total 2.0
+```
diff --git a/examples/integrations/micrometer/mp/pom.xml b/examples/integrations/micrometer/mp/pom.xml
new file mode 100644
index 000000000..07036bad9
--- /dev/null
+++ b/examples/integrations/micrometer/mp/pom.xml
@@ -0,0 +1,107 @@
+
+
+
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+ 4.0.0
+
+ Helidon Examples Integration Micrometer MP
+
+
+ Basic illustration of Micrometer integration in Helidon MP
+
+
+ io.helidon.examples.integrations.micrometer
+ helidon-examples-integrations-micrometer-mp
+ 1.0.0-SNAPSHOT
+
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile-core
+
+
+ io.helidon.integrations.micrometer
+ helidon-integrations-micrometer-cdi
+
+
+ io.helidon.logging
+ helidon-logging-jul
+ runtime
+
+
+ org.eclipse.microprofile.openapi
+ microprofile-openapi-api
+
+
+ org.glassfish.jersey.media
+ jersey-media-json-binding
+ runtime
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.microprofile.testing
+ helidon-microprofile-testing-junit5
+ test
+
+
+ io.helidon.webclient
+ helidon-webclient
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetResource.java b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetResource.java
new file mode 100644
index 000000000..063d9e686
--- /dev/null
+++ b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetResource.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.micrometer.mp;
+
+import io.micrometer.core.annotation.Counted;
+import io.micrometer.core.annotation.Timed;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
+import org.eclipse.microprofile.openapi.annotations.media.Content;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
+
+/**
+ * A simple JAX-RS resource to greet you. Examples:
+ *
+ * Get default greeting message:
+ * curl -X GET http://localhost:8080/greet
+ *
+ * Get greeting message for Joe:
+ * curl -X GET http://localhost:8080/greet/Joe
+ *
+ * Change greeting
+ * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting
+ *
+ * The message is returned as a JSON object.
+ */
+@Path("/greet")
+@RequestScoped
+public class GreetResource {
+
+ static final String PERSONALIZED_GETS_COUNTER_NAME = "personalizedGets";
+ private static final String PERSONALIZED_GETS_COUNTER_DESCRIPTION = "Counts personalized GET operations";
+ static final String GETS_TIMER_NAME = "allGets";
+ private static final String GETS_TIMER_DESCRIPTION = "Tracks all GET operations";
+
+ /**
+ * The greeting message provider.
+ */
+ private final GreetingProvider greetingProvider;
+
+ /**
+ * Using constructor injection to get a configuration property.
+ * By default this gets the value from META-INF/microprofile-config
+ *
+ * @param greetingConfig the configured greeting message
+ */
+ @Inject
+ public GreetResource(GreetingProvider greetingConfig) {
+ this.greetingProvider = greetingConfig;
+ }
+
+ /**
+ * Return a worldly greeting message.
+ *
+ * @return {@link GreetingMessage}
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Timed(value = GETS_TIMER_NAME, description = GETS_TIMER_DESCRIPTION, histogram = true)
+ public GreetingMessage getDefaultMessage() {
+ return createResponse("World");
+ }
+
+ /**
+ * Return a greeting message using the name that was provided.
+ *
+ * @param name the name to greet
+ * @return {@link GreetingMessage}
+ */
+ @Path("/{name}")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Counted(value = PERSONALIZED_GETS_COUNTER_NAME, description = PERSONALIZED_GETS_COUNTER_DESCRIPTION)
+ @Timed(value = GETS_TIMER_NAME, description = GETS_TIMER_DESCRIPTION, histogram = true)
+ public GreetingMessage getMessage(@PathParam("name") String name) {
+ return createResponse(name);
+ }
+
+ /**
+ * Set the greeting to use in future messages.
+ *
+ * @param message {@link GreetingMessage} containing the new greeting
+ * @return {@link Response}
+ */
+ @Path("/greeting")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @RequestBody(name = "greeting",
+ required = true,
+ content = @Content(mediaType = "application/json",
+ schema = @Schema(type = SchemaType.OBJECT, requiredProperties = { "greeting" })))
+ @APIResponses({
+ @APIResponse(name = "normal", responseCode = "204", description = "Greeting updated"),
+ @APIResponse(name = "missing 'greeting'", responseCode = "400",
+ description = "JSON did not contain setting for 'greeting'")})
+ public Response updateGreeting(GreetingMessage message) {
+ if (message.getMessage() == null) {
+ GreetingMessage entity = new GreetingMessage("No greeting provided");
+ return Response.status(Response.Status.BAD_REQUEST).entity(entity).build();
+ }
+
+ greetingProvider.setMessage(message.getMessage());
+ return Response.status(Response.Status.NO_CONTENT).build();
+ }
+
+ private GreetingMessage createResponse(String who) {
+ String msg = String.format("%s %s!", greetingProvider.getMessage(), who);
+ return new GreetingMessage(msg);
+ }
+}
diff --git a/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetingMessage.java b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetingMessage.java
new file mode 100644
index 000000000..e7a993ea7
--- /dev/null
+++ b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetingMessage.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2023, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.micrometer.mp;
+
+/**
+ * POJO defining the greeting message content.
+ */
+@SuppressWarnings("unused")
+public class GreetingMessage {
+ private String message;
+
+ /**
+ * Create a new GreetingMessage instance.
+ */
+ public GreetingMessage() {
+ }
+
+ /**
+ * Create a new GreetingMessage instance.
+ *
+ * @param message message
+ */
+ public GreetingMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * Gets the message value.
+ *
+ * @return message value
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Sets the message value.
+ *
+ * @param message message value to set
+ */
+ public void setMessage(String message) {
+ this.message = message;
+ }
+}
diff --git a/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetingProvider.java b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetingProvider.java
new file mode 100644
index 000000000..3630b1a3b
--- /dev/null
+++ b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/GreetingProvider.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.micrometer.mp;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+/**
+ * Provider for greeting message.
+ */
+@ApplicationScoped
+public class GreetingProvider {
+ private final AtomicReference message = new AtomicReference<>();
+
+ /**
+ * Create a new greeting provider, reading the message from configuration.
+ *
+ * @param message greeting to use
+ */
+ @Inject
+ public GreetingProvider(@ConfigProperty(name = "app.greeting") String message) {
+ this.message.set(message);
+ }
+
+ String getMessage() {
+ return message.get();
+ }
+
+ void setMessage(String message) {
+ this.message.set(message);
+ }
+}
diff --git a/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/package-info.java b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/package-info.java
new file mode 100644
index 000000000..cbcea4b58
--- /dev/null
+++ b/examples/integrations/micrometer/mp/src/main/java/io/helidon/examples/integrations/micrometer/mp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example for Micrometer integration.
+ */
+package io.helidon.examples.integrations.micrometer.mp;
diff --git a/examples/integrations/micrometer/mp/src/main/resources/META-INF/beans.xml b/examples/integrations/micrometer/mp/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..e149ced7d
--- /dev/null
+++ b/examples/integrations/micrometer/mp/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/integrations/micrometer/mp/src/main/resources/META-INF/microprofile-config.properties b/examples/integrations/micrometer/mp/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 000000000..4c7757ab7
--- /dev/null
+++ b/examples/integrations/micrometer/mp/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,25 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Application properties. This is the default greeting
+app.greeting=Hello
+
+# Microprofile server properties
+server.port=8080
+server.host=0.0.0.0
+
+micrometer.builtin-registries.0.type=prometheus
+micrometer.builtin-registries.0.prefix=myPrefix
diff --git a/examples/integrations/micrometer/mp/src/main/resources/application.yaml b/examples/integrations/micrometer/mp/src/main/resources/application.yaml
new file mode 100644
index 000000000..040ee86dd
--- /dev/null
+++ b/examples/integrations/micrometer/mp/src/main/resources/application.yaml
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+micrometer:
+ builtin-registries:
+ - type: prometheus
+ prefix: myPrefix
\ No newline at end of file
diff --git a/examples/integrations/micrometer/mp/src/main/resources/logging.properties b/examples/integrations/micrometer/mp/src/main/resources/logging.properties
new file mode 100644
index 000000000..c5299aba0
--- /dev/null
+++ b/examples/integrations/micrometer/mp/src/main/resources/logging.properties
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+# Component specific log levels
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.microprofile.level=INFO
+#io.helidon.common.level=INFO
+#org.glassfish.jersey.level=INFO
+#org.jboss.weld=INFO
diff --git a/examples/integrations/micrometer/mp/src/test/java/io/helidon/examples/integrations/micrometer/mp/TestEndpoint.java b/examples/integrations/micrometer/mp/src/test/java/io/helidon/examples/integrations/micrometer/mp/TestEndpoint.java
new file mode 100644
index 000000000..9ddafa675
--- /dev/null
+++ b/examples/integrations/micrometer/mp/src/test/java/io/helidon/examples/integrations/micrometer/mp/TestEndpoint.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.micrometer.mp;
+
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.MediaType;
+import org.junit.jupiter.api.Test;
+
+import static io.helidon.examples.integrations.micrometer.mp.GreetResource.PERSONALIZED_GETS_COUNTER_NAME;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@HelidonTest
+public class TestEndpoint {
+
+ @Inject
+ private WebTarget webTarget;
+
+ @Inject
+ private MeterRegistry registry;
+
+ @Test
+ public void pingGreet() {
+
+ GreetingMessage message = webTarget
+ .path("/greet/Joe")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .get(GreetingMessage.class);
+
+ String responseString = message.getMessage();
+
+ assertThat("Response string", responseString, is("Hello Joe!"));
+ Counter counter = registry.counter(PERSONALIZED_GETS_COUNTER_NAME);
+ double before = counter.count();
+
+ message = webTarget
+ .path("/greet/Jose")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .get(GreetingMessage.class);
+
+ responseString = message.getMessage();
+
+ assertThat("Response string", responseString, is("Hello Jose!"));
+ double after = counter.count();
+ assertThat("Difference in personalized greeting counter between successive calls", after - before, is(1d));
+
+ }
+}
diff --git a/examples/integrations/micrometer/pom.xml b/examples/integrations/micrometer/pom.xml
new file mode 100644
index 000000000..da7cb8006
--- /dev/null
+++ b/examples/integrations/micrometer/pom.xml
@@ -0,0 +1,43 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples.integrations
+ helidon-examples-integrations-project
+ 1.0.0-SNAPSHOT
+
+
+ helidon-examples-micrometer-project
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration Micrometer
+
+ pom
+
+
+ se
+ mp
+
+
+
+ Basic illustrations of Micrometer integration in Helidon
+
+
+
diff --git a/examples/integrations/micrometer/se/README.md b/examples/integrations/micrometer/se/README.md
new file mode 100644
index 000000000..2358b4b55
--- /dev/null
+++ b/examples/integrations/micrometer/se/README.md
@@ -0,0 +1,94 @@
+# Helidon SE Micrometer Example
+
+This example shows a simple greeting application, similar to the one from the
+Helidon SE QuickStart, but enhanced with Micrometer support to:
+
+* time all accesses to the two `GET` endpoints, and
+
+* count the accesses to the `GET` endpoint which returns a personalized
+ greeting.
+
+The Helidon Micrometer integration creates a Micrometer `MeterRegistry` automatically for you.
+The `registry()` instance method on `MicrometerSupport` returns that meter registry.
+
+The `Main` class
+1. Uses `MicrometerSupport` to obtain the Micrometer `MeterRegistry` which Helidon SE
+ automatically provides.
+
+1. Uses the `MeterRegistry` to:
+ * Create a Micrometer `Timer` for recording accesses to all `GET` endpoints.
+ * Create a Micrometer `Counter` for counting accesses to the `GET` endpoint that
+ returns a personalized greeting.
+
+1. Registers the built-in support for the `/micrometer` endpoint.
+
+1. Passes the `Timer` and `Counter` to the `GreetingService` constructor.
+
+The `GreetingService` class
+1. Accepts in the constructor the `Timer` and `Counter` and saves them.
+1. Adds routing rules to:
+ * Update the `Timer` with every `GET` access.
+ * Increment `Counter` (in addition to returning a personalized greeting) for every
+ personalized `GET` access.
+
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-integrations-micrometer-se.jar
+```
+
+## Using the app endpoints as with the "classic" greeting app
+
+These normal greeting app endpoints work just as in the original greeting app:
+
+```shell
+curl -X GET http://localhost:8080/greet
+#Output: {"message":"Hello World!"}
+```
+
+```shell
+curl -X GET http://localhost:8080/greet/Joe
+#Output: {"message":"Hello Joe!"}
+```
+
+```shell
+curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Hola"}' http://localhost:8080/greet/greeting
+
+curl -X GET http://localhost:8080/greet/Jose
+#Output: {"message":"Hola Jose!"}
+```
+
+## Using Micrometer
+
+Access the `/micrometer` endpoint which reports the newly-added timer and counter.
+```shell
+curl http://localhost:8080/micrometer
+```
+
+Because we created the `Timer` with a histogram,
+the actual timer output includes a lengthy histogram (only part of which is shown below).
+Your output might show the `personalizedGets` output before the `allGets` output,
+rather than after as shown here.
+
+```shell
+curl http://localhost:8080/micrometer
+```
+```listing
+# HELP allGets_seconds_max
+# TYPE allGets_seconds_max gauge
+allGets_seconds_max 0.04341847
+# HELP allGets_seconds
+# TYPE allGets_seconds histogram
+allGets_seconds_bucket{le="0.001",} 0.0
+...
+allGets_seconds_bucket{le="30.0",} 3.0
+allGets_seconds_bucket{le="+Inf",} 3.0
+allGets_seconds_count 3.0
+allGets_seconds_sum 0.049222592
+# HELP personalizedGets_total
+# TYPE personalizedGets_total counter
+personalizedGets_total 2.0
+
+```
\ No newline at end of file
diff --git a/examples/integrations/micrometer/se/pom.xml b/examples/integrations/micrometer/se/pom.xml
new file mode 100644
index 000000000..e0bf3789e
--- /dev/null
+++ b/examples/integrations/micrometer/se/pom.xml
@@ -0,0 +1,94 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+
+ io.helidon.examples.integrations.micrometer-project
+ helidon-examples-integrations-micrometer-se
+ 1.0.0-SNAPSHOT
+
+ Helidon Examples Integration Micrometer SE
+
+
+ Basic illustration of Micrometer integration in Helidon SE
+
+
+
+ io.helidon.examples.integrations.micrometer.se.Main
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.http.media
+ helidon-http-media-jsonp
+
+
+ io.helidon.integrations.micrometer
+ helidon-integrations-micrometer
+
+
+ io.helidon.webclient
+ helidon-webclient
+ test
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
diff --git a/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/integrations/micrometer/se/GreetService.java b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/integrations/micrometer/se/GreetService.java
new file mode 100644
index 000000000..b5049622b
--- /dev/null
+++ b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/integrations/micrometer/se/GreetService.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.micrometer.se;
+
+import java.util.Collections;
+
+import io.helidon.config.Config;
+import io.helidon.http.Status;
+import io.helidon.webserver.http.HttpRequest;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.Timer;
+import jakarta.json.Json;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+
+/**
+ * A simple service to greet you.
+ *
+ * Examples:
+ *
{@code
+ * Get default greeting message:
+ * curl -X GET http://localhost:8080/greet
+ *
+ * Get greeting message for Joe:
+ * curl -X GET http://localhost:8080/greet/Joe
+ *
+ * Change greeting
+ * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting
+ *
+ * }
+ * The greeting message is returned as a JSON object.
+ *
+ *
+ */
+
+public class GreetService implements HttpService {
+
+ /**
+ * The config value for the key {@code greeting}.
+ */
+ private String greeting;
+
+ private final Timer getTimer;
+ private final Counter personalizedGetCounter;
+
+ private static final JsonBuilderFactory JSON_BF = Json.createBuilderFactory(Collections.emptyMap());
+
+ GreetService(Timer getTimer, Counter personalizedGetCounter) {
+ Config config = Config.global();
+ this.greeting = config.get("app.greeting").asString().orElse("Ciao");
+ this.getTimer = getTimer;
+ this.personalizedGetCounter = personalizedGetCounter;
+ }
+
+ /**
+ * A service registers itself by updating the routine rules.
+ * @param rules the routing rules.
+ */
+ @Override
+ public void routing(HttpRules rules) {
+ rules
+ .get((req, resp) -> getTimer.record(resp::next)) // Update the timer with every GET.
+ .get("/", this::getDefaultMessageHandler)
+ .get("/{name}",
+ (req, resp) -> {
+ personalizedGetCounter.increment();
+ resp.next();
+ }, // Count personalized GETs...
+ this::getMessageHandler) // ...and process them.
+ .put("/greeting", this::updateGreetingHandler);
+ }
+
+ /**
+ * Return a worldly greeting message.
+ * @param request the server request
+ * @param response the server response
+ */
+ private void getDefaultMessageHandler(HttpRequest request,
+ ServerResponse response) {
+ sendResponse(response, "World");
+ }
+
+ /**
+ * Return a greeting message using the name that was provided.
+ * @param request the server request
+ * @param response the server response
+ */
+ private void getMessageHandler(ServerRequest request,
+ ServerResponse response) {
+ String name = request.path().pathParameters().first("name").get();
+ sendResponse(response, name);
+ }
+
+ private void sendResponse(ServerResponse response, String name) {
+ GreetingMessage msg = new GreetingMessage(String.format("%s %s!", greeting, name));
+ response.send(msg.forRest());
+ }
+
+ private void updateGreetingFromJson(JsonObject jo, ServerResponse response) {
+
+ if (!jo.containsKey(GreetingMessage.JSON_LABEL)) {
+ JsonObject jsonErrorObject = JSON_BF.createObjectBuilder()
+ .add("error", "No greeting provided")
+ .build();
+ response.status(Status.BAD_REQUEST_400)
+ .send(jsonErrorObject);
+ return;
+ }
+
+ greeting = GreetingMessage.fromRest(jo).getMessage();
+ response.status(Status.NO_CONTENT_204).send();
+ }
+
+ /**
+ * Set the greeting to use in future messages.
+ * @param request the server request
+ * @param response the server response
+ */
+ private void updateGreetingHandler(ServerRequest request,
+ ServerResponse response) {
+ JsonObject obj = request.content().as(JsonObject.class);
+ updateGreetingFromJson(obj, response);
+ }
+}
diff --git a/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/integrations/micrometer/se/GreetingMessage.java b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/integrations/micrometer/se/GreetingMessage.java
new file mode 100644
index 000000000..e05d9db24
--- /dev/null
+++ b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/integrations/micrometer/se/GreetingMessage.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.micrometer.se;
+
+import java.util.Collections;
+
+import jakarta.json.Json;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonObjectBuilder;
+
+/**
+ * POJO for the greeting message exchanged between the server and the client.
+ */
+public class GreetingMessage {
+
+ /**
+ * Label for tagging a {@code GreetingMessage} instance in JSON.
+ */
+ public static final String JSON_LABEL = "greeting";
+
+ private static final JsonBuilderFactory JSON_BF = Json.createBuilderFactory(Collections.emptyMap());
+
+ private String message;
+
+ /**
+ * Create a new greeting with the specified message content.
+ *
+ * @param message the message to store in the greeting
+ */
+ public GreetingMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * Returns the message value.
+ *
+ * @return the message
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Sets the message value.
+ *
+ * @param message value to be set
+ */
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * Converts a JSON object (typically read from the request payload)
+ * into a {@code GreetingMessage}.
+ *
+ * @param jsonObject the {@link JsonObject} to convert.
+ * @return {@code GreetingMessage} set according to the provided object
+ */
+ public static GreetingMessage fromRest(JsonObject jsonObject) {
+ return new GreetingMessage(jsonObject.getString(JSON_LABEL));
+ }
+
+ /**
+ * Prepares a {@link JsonObject} corresponding to this instance.
+ *
+ * @return {@code JsonObject} representing this {@code GreetingMessage} instance
+ */
+ public JsonObject forRest() {
+ JsonObjectBuilder builder = JSON_BF.createObjectBuilder();
+ return builder.add(JSON_LABEL, message)
+ .build();
+ }
+}
diff --git a/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/integrations/micrometer/se/Main.java b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/integrations/micrometer/se/Main.java
new file mode 100644
index 000000000..47f3bba60
--- /dev/null
+++ b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/integrations/micrometer/se/Main.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.micrometer.se;
+
+import io.helidon.config.Config;
+import io.helidon.integrations.micrometer.MicrometerFeature;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.http.HttpRouting;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.Timer;
+
+/**
+ * Simple Hello World rest application.
+ */
+public final class Main {
+
+ static final String PERSONALIZED_GETS_COUNTER_NAME = "personalizedGets";
+ static final String ALL_GETS_TIMER_NAME = "allGets";
+
+ /**
+ * Cannot be instantiated.
+ */
+ private Main() {
+ }
+
+ /**
+ * Application main entry point.
+ *
+ * @param args command line arguments.
+ */
+ public static void main(final String[] args) {
+ startServer();
+ }
+
+ /**
+ * Start the server.
+ */
+ static WebServer startServer() {
+
+ // load logging configuration
+ LogConfig.configureRuntime();
+
+ // By default, this will pick up application.yaml from the classpath
+ // and initialize global config
+ Config config = Config.create();
+ Config.global(config);
+
+ WebServer server = WebServer.builder()
+ .config(config.get("server"))
+ .routing(Main::setupRouting)
+ .build()
+ .start();
+
+ System.out.println("WEB server is up! http://localhost:" + server.port() + "/greet");
+ return server;
+ }
+
+ /**
+ * Setup routing.
+ *
+ * @param routing routing builder
+ */
+ static void setupRouting(HttpRouting.Builder routing) {
+
+ Config config = Config.global();
+
+ MicrometerFeature micrometerSupport = MicrometerFeature.create(config);
+ Counter personalizedGetCounter = micrometerSupport.registry()
+ .counter(PERSONALIZED_GETS_COUNTER_NAME);
+ Timer getTimer = Timer.builder(ALL_GETS_TIMER_NAME)
+ .publishPercentileHistogram()
+ .register(micrometerSupport.registry());
+
+ GreetService greetService = new GreetService(getTimer, personalizedGetCounter);
+
+ routing.register("/greet", greetService)
+ .addFeature(micrometerSupport);
+ }
+}
diff --git a/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/integrations/micrometer/se/package-info.java b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/integrations/micrometer/se/package-info.java
new file mode 100644
index 000000000..e58edb7ca
--- /dev/null
+++ b/examples/integrations/micrometer/se/src/main/java/io/helidon/examples/integrations/micrometer/se/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example application showing Micrometer support in Helidon SE
+ *
+ * Start with {@link io.helidon.examples.integrations.micrometer.se.Main} class.
+ *
+ * @see io.helidon.examples.integrations.micrometer.se.Main
+ */
+package io.helidon.examples.integrations.micrometer.se;
diff --git a/examples/integrations/micrometer/se/src/main/resources/application.yaml b/examples/integrations/micrometer/se/src/main/resources/application.yaml
new file mode 100644
index 000000000..92e9f51e4
--- /dev/null
+++ b/examples/integrations/micrometer/se/src/main/resources/application.yaml
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+app:
+ greeting: "Hello"
+
+server:
+ port: 8080
+ host: 0.0.0.0
+
+
diff --git a/examples/integrations/micrometer/se/src/test/java/io/helidon/examples/integrations/micrometer/se/MainTest.java b/examples/integrations/micrometer/se/src/test/java/io/helidon/examples/integrations/micrometer/se/MainTest.java
new file mode 100644
index 000000000..5e179a824
--- /dev/null
+++ b/examples/integrations/micrometer/se/src/test/java/io/helidon/examples/integrations/micrometer/se/MainTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.micrometer.se;
+
+import java.util.Collections;
+
+import io.helidon.config.Config;
+import io.helidon.http.Status;
+import io.helidon.webclient.http1.Http1Client;
+import io.helidon.webclient.http1.Http1ClientResponse;
+import io.helidon.webserver.WebServerConfig.Builder;
+import io.helidon.webserver.testing.junit5.ServerTest;
+import io.helidon.webserver.testing.junit5.SetUpServer;
+
+import jakarta.json.Json;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+// we need to first call the methods, before validating metrics
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+@ServerTest
+public class MainTest {
+
+ private static final JsonBuilderFactory JSON_BF = Json.createBuilderFactory(Collections.emptyMap());
+ private static final JsonObject TEST_JSON_OBJECT;
+
+ private static double expectedPersonalizedGets;
+ private static double expectedAllGets;
+ private final Http1Client client;
+
+ static {
+ TEST_JSON_OBJECT = JSON_BF.createObjectBuilder()
+ .add("greeting", "Hola")
+ .build();
+ }
+
+ public MainTest(Http1Client client) {
+ this.client = client;
+ }
+
+ @SetUpServer
+ public static void setup(Builder builder) {
+ builder.routing(Main::setupRouting);
+ }
+
+ @Test
+ @Order(1)
+ void testDefaultGreeting() {
+ JsonObject jsonObject = get();
+ assertThat(jsonObject.getString("greeting"), is("Hello World!"));
+ }
+
+ @Test
+ @Order(2)
+ void testNamedGreeting() {
+ JsonObject jsonObject = personalizedGet("Joe");
+ Assertions.assertEquals("Hello Joe!", jsonObject.getString("greeting"));
+ }
+
+ @Test
+ @Order(3)
+ void testUpdateGreeting() {
+ try (Http1ClientResponse response = client.put()
+ .path("/greet/greeting")
+ .submit(TEST_JSON_OBJECT)) {
+
+ assertThat(response.status(), is(Status.NO_CONTENT_204));
+ }
+
+ JsonObject jsonObject = personalizedGet("Joe");
+ assertThat(jsonObject.getString("greeting"), is("Hola Joe!"));
+ }
+
+ @Test
+ @Order(4)
+ void testMicrometer() {
+ Http1ClientResponse response = client.get()
+ .path("/micrometer")
+ .request();
+
+ assertThat(response.status().code(), is(200));
+
+ String output = response.as(String.class);
+ String expected = Main.ALL_GETS_TIMER_NAME + "_seconds_count " + expectedAllGets;
+ assertThat("Unable to find expected all-gets timer count " + expected + "; output is " + output,
+ output, containsString(expected)); // all gets; the put
+ // is not counted
+ assertThat("Unable to find expected all-gets timer sum", output,
+ containsString(Main.ALL_GETS_TIMER_NAME + "_seconds_sum"));
+ expected = Main.PERSONALIZED_GETS_COUNTER_NAME + "_total " + expectedPersonalizedGets;
+ assertThat("Unable to find expected counter result " + expected + "; output is " + output,
+ output, containsString(expected));
+ response.close();
+ }
+
+ private JsonObject get() {
+ return get("/greet");
+ }
+
+ private JsonObject get(String path) {
+ JsonObject jsonObject = client.get()
+ .path(path)
+ .requestEntity(JsonObject.class);
+ expectedAllGets++;
+ return jsonObject;
+ }
+
+ private JsonObject personalizedGet(String name) {
+ JsonObject result = get("/greet/" + name);
+ expectedPersonalizedGets++;
+ return result;
+ }
+}
diff --git a/examples/integrations/micronaut/data/README.md b/examples/integrations/micronaut/data/README.md
new file mode 100644
index 000000000..52b780e84
--- /dev/null
+++ b/examples/integrations/micronaut/data/README.md
@@ -0,0 +1,146 @@
+# Helidon Micronaut Data Example
+
+This example shows integration with Micronaut Data into Helidon MP.
+
+## Sources
+
+This example combines Micronaut Data and CDI.
+
+### CDI classes
+
+The following classes are CDI and JAX-RS classes that use injected Micronaut beans:
+
+- `PetResource` - JAX-RS resource exposing Pet REST API
+- `OwnerResource` - JAX-RS resource exposing Owner REST API
+- `BeanValidationExceptionMapper` - JAX-RS exception mapper to return correct status code
+ in case of validation failure
+
+### Micronaut classes
+
+The following classes are pure Micronaut beans (and cannot have CDI injected into them)
+
+- `DbPetRepository` - Micronaut Data repository extending an abstract class
+- `DbOwnerRepository` - Micronaut Data repository implementing an interface
+- `DbPopulateData` - Micronaut startup event listener to initialize the database
+- package `model` - data model of the database
+
+## Build and run
+
+Start the application:
+
+```shell
+mvn package
+java -jar target/helidon-examples-integrations-micronaut-data.jar
+```
+
+Access endpoints
+
+```shell
+# Get all pets
+curl -i http://localhost:8080/pets
+# Get all owners
+curl -i http://localhost:8080/owners
+# Get a single pet
+curl -i http://localhost:8080/pets/Dino
+# Get a single owner
+curl -i http://localhost:8080/owners/Barney
+# To fail input validation
+curl -i http://localhost:8080/pets/s
+```
+
+# To use Oracle XE instead of H2
+
+- Update ./pom.xml to replace dependency on micronaut-jdbc-hikari with following
+```xml
+
+ io.micronaut.sql
+ micronaut-jdbc-ucp
+ runtime
+
+```
+
+- Update ./pom.xml to replace dependency on com.h2database with following
+```xml
+
+ io.helidon.integrations.db
+ ojdbc
+ runtime
+
+
+ com.oracle.database.jdbc
+ ucp
+ runtime
+
+```
+
+- Update ./src/main/java/io/helidon/examples/integrations/micronaut/data/DbOwnerRepository.java and
+ ./src/main/java/io/helidon/examples/integrations/micronaut/data/DbOwnerRepository.java to change from
+ Dialect.H2 to Dialect.ORACLE
+
+- Update ./src/main/java/io/helidon/examples/integrations/micronaut/data/DbPopulateData.java to change Typehint to
+ @TypeHint(typeNames = {"oracle.jdbc.OracleDriver"})
+
+- Install Oracle XE
+ Instructions for running XE in a docker container can be found here: https://github.com/oracle/docker-images/tree/master/OracleDatabase/SingleInstance
+
+- Update ./src/main/resources/META-INF/microprofile-config.properties to comment out h2 related datasource.* properties and uncomment+update following ones related to XE.
+```properties
+#datasources.default.url=jdbc:oracle:thin:@localhost:/
+#datasources.default.driverClassName=oracle.jdbc.OracleDriver
+#datasources.default.username=system
+#datasources.default.password=
+#datasources.default.schema-generate=CREATE_DROP
+#datasources.default.dialect=oracle
+```
+
+# To use Oracle ATP cloud service instead of H2
+
+- Update ./pom.xml to replace dependency on micronaut-jdbc-hikari with following
+```xml
+
+ io.micronaut.sql
+ micronaut-jdbc-ucp
+ runtime
+
+```
+
+- Update ./pom.xml to replace dependency on com.h2database with following
+```xml
+
+ io.helidon.integrations.db
+ ojdbc
+ runtime
+
+
+ com.oracle.database.jdbc
+ ucp
+ runtime
+
+```
+
+- Update ./src/main/java/io/helidon/examples/integrations/micronaut/data/DbOwnerRepository.java and
+ ./src/main/java/io/helidon/examples/integrations/micronaut/data/DbOwnerRepository.java to change from
+ Dialect.H2 to Dialect.ORACLE
+
+- Update ./src/main/java/io/helidon/examples/integrations/micronaut/data/DbPopulateData.java to change Typehint to
+ @TypeHint(typeNames = {"oracle.jdbc.OracleDriver"})
+
+- Setup ATP
+ Instructions for ATP setup can be found here: https://blogs.oracle.com/developers/the-complete-guide-to-getting-up-and-running-with-autonomous-database-in-the-cloud
+
+- Create Schema used by test
+```sql
+CREATE TABLE "PET" ("ID" VARCHAR(36),"OWNER_ID" NUMBER(19) NOT NULL,"NAME" VARCHAR(255) NOT NULL,"TYPE" VARCHAR(255) NOT NULL);
+CREATE SEQUENCE "OWNER_SEQ" MINVALUE 1 START WITH 1 NOCACHE NOCYCLE;
+CREATE TABLE "OWNER" ("ID" NUMBER(19) PRIMARY KEY NOT NULL,"AGE" NUMBER(10) NOT NULL,"NAME" VARCHAR(255) NOT NULL);
+```
+
+- Update ./src/main/resources/META-INF/microprofile-config.properties to comment out h2 related datasource.* properties and add uncomment+update following ones related to ATP.
+```properties
+#datasources.default.url=jdbc:oracle:thin:@?TNS_ADMIN=
+#datasources.default.driverClassName=oracle.jdbc.OracleDriver
+#datasources.default.username=
+#datasources.default.password=
+#datasources.default.schema-generate=NONE
+#datasources.default.dialect=oracle
+```
\ No newline at end of file
diff --git a/examples/integrations/micronaut/data/pom.xml b/examples/integrations/micronaut/data/pom.xml
new file mode 100644
index 000000000..3b09d250e
--- /dev/null
+++ b/examples/integrations/micronaut/data/pom.xml
@@ -0,0 +1,162 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+ helidon-examples-integrations-micronaut-data
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration Micronaut Data
+
+
+
+ jakarta.persistence
+ jakarta.persistence-api
+ provided
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile
+
+
+ io.micronaut.data
+ micronaut-data-jdbc
+
+
+ io.micronaut
+ micronaut-runtime
+
+
+ io.helidon.logging
+ helidon-logging-jul
+ runtime
+
+
+ io.helidon.integrations.micronaut
+ helidon-integrations-micronaut-cdi
+ runtime
+
+
+ io.helidon.integrations.micronaut
+ helidon-integrations-micronaut-data
+ runtime
+
+
+ io.micronaut.data
+ micronaut-data-tx
+ runtime
+
+
+ io.micronaut.sql
+ micronaut-jdbc-hikari
+ runtime
+
+
+ com.h2database
+
+
+ h2
+ runtime
+
+
+ io.smallrye
+ jandex
+ runtime
+ true
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.microprofile.testing
+ helidon-microprofile-testing-junit5
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+
+ io.micronaut
+ micronaut-inject-java
+ ${version.lib.micronaut}
+
+
+ io.micronaut
+ micronaut-validation
+ ${version.lib.micronaut}
+
+
+ io.micronaut.data
+ micronaut-data-processor
+ ${version.lib.micronaut.data}
+
+
+ io.helidon.integrations.micronaut
+ helidon-integrations-micronaut-cdi-processor
+ ${helidon.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
diff --git a/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/BeanValidationExceptionMapper.java b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/BeanValidationExceptionMapper.java
new file mode 100644
index 000000000..4d184c4bc
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/BeanValidationExceptionMapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.micronaut.data;
+
+import javax.validation.ConstraintViolationException;
+
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+/**
+ * A JAX-RS provider that maps {@link jakarta.validation.ConstraintViolationException} from bean validation
+ * to a proper JAX-RS response with {@link jakarta.ws.rs.core.Response.Status#BAD_REQUEST} status.
+ * If this provider is not present, validation exception from Micronaut would end with an internal server
+ * error.
+ */
+@Provider
+public class BeanValidationExceptionMapper implements ExceptionMapper {
+ @Override
+ public Response toResponse(ConstraintViolationException exception) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(exception.getMessage())
+ .build();
+ }
+}
diff --git a/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/DbOwnerRepository.java b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/DbOwnerRepository.java
new file mode 100644
index 000000000..84a6f55d2
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/DbOwnerRepository.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.micronaut.data;
+
+import java.util.List;
+import java.util.Optional;
+
+import io.helidon.examples.integrations.micronaut.data.model.Owner;
+
+import io.micronaut.data.jdbc.annotation.JdbcRepository;
+import io.micronaut.data.model.query.builder.sql.Dialect;
+import io.micronaut.data.repository.CrudRepository;
+
+/**
+ * Micronaut Data repository for pet owners.
+ */
+@JdbcRepository(dialect = Dialect.H2)
+public interface DbOwnerRepository extends CrudRepository {
+ /**
+ * Get all owners from the database.
+ *
+ * @return all owners
+ */
+ @Override
+ List findAll();
+
+ /**
+ * Find an owner by name.
+ *
+ * @param name name of owner
+ * @return owner if found
+ */
+ Optional findByName(String name);
+}
diff --git a/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/DbPetRepository.java b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/DbPetRepository.java
new file mode 100644
index 000000000..a02814655
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/DbPetRepository.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.micronaut.data;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import io.helidon.examples.integrations.micronaut.data.model.NameDTO;
+import io.helidon.examples.integrations.micronaut.data.model.Pet;
+
+import io.micronaut.data.annotation.Join;
+import io.micronaut.data.jdbc.annotation.JdbcRepository;
+import io.micronaut.data.model.Pageable;
+import io.micronaut.data.model.query.builder.sql.Dialect;
+import io.micronaut.data.repository.PageableRepository;
+
+/**
+ * Micronaut data repository for pets.
+ */
+@JdbcRepository(dialect = Dialect.H2)
+public abstract class DbPetRepository implements PageableRepository {
+
+ /**
+ * Get all pets.
+ *
+ * @param pageable pageable instance
+ * @return list of pets
+ */
+ public abstract List list(Pageable pageable);
+
+ /**
+ * Find a pet by its name.
+ *
+ * @param name pet name
+ * @return pet if it was found
+ */
+ @Join("owner")
+ public abstract Optional findByName(String name);
+}
diff --git a/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/DbPopulateData.java b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/DbPopulateData.java
new file mode 100644
index 000000000..dcd86a40f
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/DbPopulateData.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 class is almost exactly copied from Micronaut examples.
+ */
+package io.helidon.examples.integrations.micronaut.data;
+
+import java.util.Arrays;
+
+import io.helidon.examples.integrations.micronaut.data.model.Owner;
+import io.helidon.examples.integrations.micronaut.data.model.Pet;
+
+import io.micronaut.context.event.StartupEvent;
+import io.micronaut.core.annotation.TypeHint;
+import io.micronaut.runtime.event.annotation.EventListener;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+
+/**
+ * A Micronaut bean that listens on startup event and populates database with data.
+ */
+@Singleton
+@TypeHint(typeNames = {"org.h2.Driver", "org.h2.mvstore.db.MVTableEngine"})
+public class DbPopulateData {
+ private final DbOwnerRepository ownerRepository;
+ private final DbPetRepository petRepository;
+
+ @Inject
+ DbPopulateData(DbOwnerRepository ownerRepository, DbPetRepository petRepository) {
+ this.ownerRepository = ownerRepository;
+ this.petRepository = petRepository;
+ }
+
+ @EventListener
+ void init(StartupEvent event) {
+ Owner fred = new Owner("Fred");
+ fred.setAge(45);
+ Owner barney = new Owner("Barney");
+ barney.setAge(40);
+ ownerRepository.saveAll(Arrays.asList(fred, barney));
+
+ Pet dino = new Pet("Dino", fred);
+ Pet bp = new Pet("Baby Puss", fred);
+ bp.setType(Pet.PetType.CAT);
+ Pet hoppy = new Pet("Hoppy", barney);
+
+ petRepository.saveAll(Arrays.asList(dino, bp, hoppy));
+ }
+}
diff --git a/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/OwnerResource.java b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/OwnerResource.java
new file mode 100644
index 000000000..58aaca488
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/OwnerResource.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.micronaut.data;
+
+import io.helidon.examples.integrations.micronaut.data.model.Owner;
+
+import jakarta.inject.Inject;
+import jakarta.validation.constraints.Pattern;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import org.eclipse.microprofile.metrics.annotation.Timed;
+
+/**
+ * JAX-RS resource, and the MicroProfile entry point to manage pet owners.
+ * This resource used Micronaut data beans (repositories) to query database, and
+ * bean validation as implemented by Micronaut.
+ */
+@Path("/owners")
+public class OwnerResource {
+ private final DbOwnerRepository ownerRepository;
+
+ /**
+ * Create a new instance with repository.
+ *
+ * @param ownerRepo owner repository from Micronaut data
+ */
+ @Inject
+ public OwnerResource(DbOwnerRepository ownerRepo) {
+ this.ownerRepository = ownerRepo;
+ }
+
+ /**
+ * Gets all owners from the database.
+ * @return all owners, using JSON-B to map them to JSON
+ */
+ @GET
+ public Iterable getAll() {
+ return ownerRepository.findAll();
+ }
+
+ /**
+ * Get a named owner from the database.
+ *
+ * @param name name of the owner to find, must be at least two characters long, may contain whitespace
+ * @return a single owner
+ * @throws jakarta.ws.rs.NotFoundException in case the owner is not in the database (to return 404 status)
+ */
+ @Path("/{name}")
+ @GET
+ @Timed
+ public Owner owner(@PathParam("name") @Pattern(regexp = "\\w+[\\w+\\s?]*\\w") String name) {
+ return ownerRepository.findByName(name)
+ .orElseThrow(() -> new NotFoundException("Owner by name " + name + " does not exist"));
+ }
+}
diff --git a/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/PetResource.java b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/PetResource.java
new file mode 100644
index 000000000..a5fea526e
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/PetResource.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.micronaut.data;
+
+import javax.validation.constraints.Pattern;
+
+import io.helidon.examples.integrations.micronaut.data.model.Pet;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import org.eclipse.microprofile.metrics.annotation.Timed;
+
+/**
+ * JAX-RS resource, and the MicroProfile entry point to manage pets.
+ * This resource used Micronaut data beans (repositories) to query database, and
+ * bean validation as implemented by Micronaut.
+ */
+@Path("/pets")
+public class PetResource {
+ private final DbPetRepository petRepository;
+
+ /**
+ * Create a new instance with pet repository.
+ *
+ * @param petRepo Pet repository from Micronaut data
+ */
+ @Inject
+ public PetResource(DbPetRepository petRepo) {
+ this.petRepository = petRepo;
+ }
+
+ /**
+ * Gets all pets from the database.
+ * @return all pets, using JSON-B to map them to JSON
+ */
+ @GET
+ public Iterable getAll() {
+ return petRepository.findAll();
+ }
+
+ /**
+ * Get a named pet from the database.
+ *
+ * @param name name of the pet to find, must be at least two characters long, may contain whitespace
+ * @return a single pet
+ * @throws jakarta.ws.rs.NotFoundException in case the pet is not in the database (to return 404 status)
+ */
+ @Path("/{name}")
+ @GET
+ @Timed
+ public Pet pet(@PathParam("name") @Pattern(regexp = "\\w+[\\w+\\s?]*\\w") String name) {
+ return petRepository.findByName(name)
+ .orElseThrow(() -> new NotFoundException("Pet by name " + name + " does not exist"));
+ }
+}
diff --git a/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/model/NameDTO.java b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/model/NameDTO.java
new file mode 100644
index 000000000..35a119c27
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/model/NameDTO.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.micronaut.data.model;
+
+import io.micronaut.core.annotation.Introspected;
+
+/**
+ * Used in list of names of pets.
+ */
+@Introspected
+public class NameDTO {
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/model/Owner.java b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/model/Owner.java
new file mode 100644
index 000000000..37591b873
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/model/Owner.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.micronaut.data.model;
+
+import io.micronaut.core.annotation.Creator;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.Id;
+
+/**
+ * Owner database entity.
+ */
+@Entity
+public class Owner {
+
+ @Id
+ @GeneratedValue
+ private Long id;
+ private String name;
+ private int age;
+
+ /**
+ * Create a named owner.
+ *
+ * @param name name of the owner
+ */
+ @Creator
+ public Owner(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+}
diff --git a/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/model/Pet.java b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/model/Pet.java
new file mode 100644
index 000000000..2b8858bc4
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/model/Pet.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.micronaut.data.model;
+
+import java.util.UUID;
+
+import io.micronaut.core.annotation.Creator;
+import io.micronaut.core.annotation.Nullable;
+import io.micronaut.data.annotation.AutoPopulated;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.ManyToOne;
+
+/**
+ * Pet database entity.
+ */
+@Entity
+public class Pet {
+
+ @Id
+ @AutoPopulated
+ private UUID id;
+ private String name;
+ @ManyToOne
+ private Owner owner;
+ private PetType type = PetType.DOG;
+
+ /**
+ * Creates a new pet.
+ * @param name name of the pet
+ * @param owner owner of the pet (optional)
+ */
+ // NOTE - please use Nullable from this package, jakarta.annotation.Nullable will fail with JPMS,
+ // as it is declared in the same package as is used by annother module (jakarta.annotation-api)
+ @Creator
+ public Pet(String name, @Nullable Owner owner) {
+ this.name = name;
+ this.owner = owner;
+ }
+
+ public Owner getOwner() {
+ return owner;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public UUID getId() {
+ return id;
+ }
+
+ public PetType getType() {
+ return type;
+ }
+
+ public void setType(PetType type) {
+ this.type = type;
+ }
+
+ public void setId(UUID id) {
+ this.id = id;
+ }
+
+ /**
+ * Type of pet.
+ */
+ public enum PetType {
+ /**
+ * Dog.
+ */
+ DOG,
+ /**
+ * Cat.
+ */
+ CAT
+ }
+}
diff --git a/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/model/package-info.java b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/model/package-info.java
new file mode 100644
index 000000000..c5cd8a9a3
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/model/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Model classes for entities and transfer objects.
+ */
+package io.helidon.examples.integrations.micronaut.data.model;
diff --git a/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/package-info.java b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/package-info.java
new file mode 100644
index 000000000..d11096061
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/java/io/helidon/examples/integrations/micronaut/data/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example showing use of Micronaut Data in Helidon MicroProfile server.
+ */
+package io.helidon.examples.integrations.micronaut.data;
diff --git a/examples/integrations/micronaut/data/src/main/resources/META-INF/beans.xml b/examples/integrations/micronaut/data/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..676e09a2d
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/integrations/micronaut/data/src/main/resources/META-INF/microprofile-config.properties b/examples/integrations/micronaut/data/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 000000000..6b196be88
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,40 @@
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server.port=8080
+
+#When native image is used, we must use remote h2
+#datasources.default.url=jdbc:h2:tcp://localhost:9092/test
+datasources.default.url=jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
+datasources.default.driverClassName=org.h2.Driver
+datasources.default.username=sa
+datasources.default.password=${EMPTY}
+datasources.default.schema-generate=CREATE_DROP
+datasources.default.dialect=h2
+
+#datasources.default.url=jdbc:oracle:thin:@localhost:/
+#datasources.default.driverClassName=oracle.jdbc.OracleDriver
+#datasources.default.username=system
+#datasources.default.password=
+#datasources.default.schema-generate=CREATE_DROP
+#datasources.default.dialect=oracle
+
+#datasources.default.url=jdbc:oracle:thin:@?TNS_ADMIN=
+#datasources.default.driverClassName=oracle.jdbc.OracleDriver
+#datasources.default.username=
+#datasources.default.password=
+#datasources.default.schema-generate=NONE
+#datasources.default.dialect=oracle
diff --git a/examples/integrations/micronaut/data/src/main/resources/logging.properties b/examples/integrations/micronaut/data/src/main/resources/logging.properties
new file mode 100644
index 000000000..d1f6eb1e5
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/main/resources/logging.properties
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+io.helidon.level=INFO
diff --git a/examples/integrations/micronaut/data/src/test/java/io/helidon/examples/integrations/micronaut/data/MicronautExampleTest.java b/examples/integrations/micronaut/data/src/test/java/io/helidon/examples/integrations/micronaut/data/MicronautExampleTest.java
new file mode 100644
index 000000000..e725cbb1f
--- /dev/null
+++ b/examples/integrations/micronaut/data/src/test/java/io/helidon/examples/integrations/micronaut/data/MicronautExampleTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.micronaut.data;
+
+import io.helidon.examples.integrations.micronaut.data.model.Pet;
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+
+import jakarta.inject.Inject;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Response;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@HelidonTest
+class MicronautExampleTest {
+ @Inject
+ private WebTarget webTarget;
+
+ @Test
+ void testAllPets() {
+ JsonArray jsonValues = webTarget.path("/pets")
+ .request()
+ .get(JsonArray.class);
+
+ assertThat("We should get all pets", jsonValues.size(), is(3));
+ }
+
+ @Test
+ void testGetPet() {
+ JsonObject pet = webTarget.path("/pets/Dino")
+ .request()
+ .get(JsonObject.class);
+
+ assertThat(pet.getString("name"), is("Dino"));
+ assertThat(pet.getString("type"), is(Pet.PetType.DOG.toString()));
+ }
+
+ @Test
+ void testNotFound() {
+ try (Response response = webTarget.path("/pets/Fino")
+ .request()
+ .get()) {
+ assertThat("Should be not found: 404", response.getStatus(), is(404));
+ }
+ }
+
+ @Test
+ void testValidationError() {
+ try (Response response = webTarget.path("/pets/a")
+ .request()
+ .get()) {
+ assertThat("Should be bad request: 400", response.getStatus(), is(400));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/examples/integrations/micronaut/pom.xml b/examples/integrations/micronaut/pom.xml
new file mode 100644
index 000000000..1eb9088ec
--- /dev/null
+++ b/examples/integrations/micronaut/pom.xml
@@ -0,0 +1,38 @@
+
+
+
+ 4.0.0
+
+ io.helidon.examples.integrations
+ helidon-examples-integrations-project
+ 1.0.0-SNAPSHOT
+
+ io.helidon.examples.integrations.micronaut
+ helidon-examples-integrations-micronaut-project
+ 1.0.0-SNAPSHOT
+ pom
+ Helidon Examples Integration Micronaut
+
+
+ data
+
+
diff --git a/examples/integrations/microstream/README.md b/examples/integrations/microstream/README.md
new file mode 100644
index 000000000..d48be2072
--- /dev/null
+++ b/examples/integrations/microstream/README.md
@@ -0,0 +1 @@
+# Microstream Integrations Examples
diff --git a/examples/integrations/microstream/greetings-mp/README.md b/examples/integrations/microstream/greetings-mp/README.md
new file mode 100644
index 000000000..b298250df
--- /dev/null
+++ b/examples/integrations/microstream/greetings-mp/README.md
@@ -0,0 +1,28 @@
+# Microstream integration example
+
+This example uses Microstream to persist the greetings supplied
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-integrations-microstream-greetings-mp.jar
+```
+
+## Endpoints
+
+Get default greeting message:
+```shell
+curl -X GET http://localhost:7001/greet
+```
+
+Get greeting message for Joe:
+
+```shell
+curl -X GET http://localhost:7001/greet/Joe
+```
+
+Add a greeting:
+```shell
+curl -X PUT -H "Content-Type: application/json" -d '{"message" : "Howdy"}' http://localhost:7001/greet/greeting
+```
\ No newline at end of file
diff --git a/examples/integrations/microstream/greetings-mp/pom.xml b/examples/integrations/microstream/greetings-mp/pom.xml
new file mode 100644
index 000000000..1983f45e7
--- /dev/null
+++ b/examples/integrations/microstream/greetings-mp/pom.xml
@@ -0,0 +1,84 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+
+ helidon-examples-integrations-microstream-greetings-mp
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration Microstream Greetings mp
+
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile
+
+
+ io.helidon.integrations.microstream
+ helidon-integrations-microstream-cdi
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.microprofile.testing
+ helidon-microprofile-testing-junit5
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/integrations/microstream/greetings-mp/src/main/java/io/helidon/examples/integrations/microstream/greetings/mp/GreetResource.java b/examples/integrations/microstream/greetings-mp/src/main/java/io/helidon/examples/integrations/microstream/greetings/mp/GreetResource.java
new file mode 100644
index 000000000..56ed25715
--- /dev/null
+++ b/examples/integrations/microstream/greetings-mp/src/main/java/io/helidon/examples/integrations/microstream/greetings/mp/GreetResource.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.microstream.greetings.mp;
+
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * A simple service to greet you. Examples:
+ *
+ * Get default greeting message:
+ * curl -X GET http://localhost:7001/greet
+ *
+ * Get greeting message for Joe:
+ * curl -X GET http://localhost:7001/greet/Joe
+ *
+ * add a greeting
+ * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:7001/greet/greeting
+ *
+ * The message is returned as a JSON object
+ */
+@Path("/greet")
+@RequestScoped
+public class GreetResource {
+
+ private final GreetingProvider greetingProvider;
+
+ /**
+ * Using constructor injection to get a configuration property.
+ * By default this gets the value from META-INF/microprofile-config
+ *
+ * @param greetingConfig the configured greeting message
+ */
+ @Inject
+ public GreetResource(GreetingProvider greetingConfig) {
+ this.greetingProvider = greetingConfig;
+ }
+
+ /**
+ * Return a default greeting message.
+ *
+ * @return {@link GreetingMessage}
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public GreetingMessage getDefaultMessage() {
+ return createResponse("World");
+ }
+
+ private GreetingMessage createResponse(String who) {
+ String msg = String.format("%s %s!", greetingProvider.getGreeting(), who);
+
+ return new GreetingMessage(msg);
+ }
+
+ /**
+ * Return a greeting message using the name that was provided.
+ *
+ * @param name the name to greet
+ * @return {@link GreetingMessage}
+ */
+ @Path("/{name}")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public GreetingMessage getMessage(@PathParam("name") String name) {
+ return createResponse(name);
+ }
+
+ /**
+ * Set the greeting to use in future messages.
+ *
+ * @param message JSON containing the new greeting
+ * @return {@link Response}
+ */
+ @Path("/greeting")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updateGreeting(GreetingMessage message) {
+ if (message.getMessage() == null) {
+ GreetingMessage entity = new GreetingMessage("No greeting provided");
+ return Response.status(Response.Status.BAD_REQUEST).entity(entity).build();
+ }
+
+ greetingProvider.addGreeting(message.getMessage());
+ return Response.status(Response.Status.NO_CONTENT).build();
+ }
+
+}
diff --git a/examples/integrations/microstream/greetings-mp/src/main/java/io/helidon/examples/integrations/microstream/greetings/mp/GreetingMessage.java b/examples/integrations/microstream/greetings-mp/src/main/java/io/helidon/examples/integrations/microstream/greetings/mp/GreetingMessage.java
new file mode 100644
index 000000000..389424b85
--- /dev/null
+++ b/examples/integrations/microstream/greetings-mp/src/main/java/io/helidon/examples/integrations/microstream/greetings/mp/GreetingMessage.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2023, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.microstream.greetings.mp;
+
+/**
+ * POJO defining the greeting message content.
+ */
+@SuppressWarnings("unused")
+public class GreetingMessage {
+ private String message;
+
+ /**
+ * Create a new GreetingMessage instance.
+ */
+ public GreetingMessage() {
+ }
+
+ /**
+ * Create a new GreetingMessage instance.
+ *
+ * @param message message
+ */
+ public GreetingMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * Gets the message value.
+ *
+ * @return message value
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Sets the message value.
+ *
+ * @param message message value to set
+ */
+ public void setMessage(String message) {
+ this.message = message;
+ }
+}
diff --git a/examples/integrations/microstream/greetings-mp/src/main/java/io/helidon/examples/integrations/microstream/greetings/mp/GreetingProvider.java b/examples/integrations/microstream/greetings-mp/src/main/java/io/helidon/examples/integrations/microstream/greetings/mp/GreetingProvider.java
new file mode 100644
index 000000000..777c32960
--- /dev/null
+++ b/examples/integrations/microstream/greetings-mp/src/main/java/io/helidon/examples/integrations/microstream/greetings/mp/GreetingProvider.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.microstream.greetings.mp;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import io.helidon.integrations.microstream.cdi.MicrostreamStorage;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import one.microstream.storage.embedded.types.EmbeddedStorageManager;
+
+/**
+ * Provider for greeting message that are persisted by microstream.
+ */
+@ApplicationScoped
+public class GreetingProvider {
+
+ private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
+ private final EmbeddedStorageManager storage;
+ private final Random rnd = new Random();
+
+ private List greetingMessages;
+
+ /**
+ * Creates new GreetingProvider using a microstream EmbeddedStorageManager.
+ *
+ * @param storage the used EmbeddedStorageManager.
+ */
+ @SuppressWarnings("unchecked")
+ @Inject
+ public GreetingProvider(@MicrostreamStorage(configNode = "one.microstream.storage.greetings")
+ EmbeddedStorageManager storage) {
+ super();
+ this.storage = storage;
+
+ // load stored data
+ greetingMessages = (List) storage.root();
+
+ // Initialize storage if empty
+ if (greetingMessages == null) {
+ greetingMessages = new ArrayList<>();
+ storage.setRoot(greetingMessages);
+ storage.storeRoot();
+ addGreeting("Hello");
+ }
+ }
+
+ /**
+ * Add a new greeting to the available greetings and persist it.
+ *
+ * @param newGreeting the new greeting to be added and persisted.
+ */
+ public void addGreeting(String newGreeting) {
+ try {
+ lock.writeLock().lock();
+ greetingMessages.add(newGreeting);
+ storage.store(greetingMessages);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * returns a random greeting.
+ *
+ * @return a greeting.
+ */
+ public String getGreeting() {
+ try {
+ lock.readLock().lock();
+ return greetingMessages.get(rnd.nextInt(greetingMessages.size()));
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+}
diff --git a/examples/integrations/microstream/greetings-mp/src/main/java/io/helidon/examples/integrations/microstream/greetings/mp/package-info.java b/examples/integrations/microstream/greetings-mp/src/main/java/io/helidon/examples/integrations/microstream/greetings/mp/package-info.java
new file mode 100644
index 000000000..e43f651b0
--- /dev/null
+++ b/examples/integrations/microstream/greetings-mp/src/main/java/io/helidon/examples/integrations/microstream/greetings/mp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * An example that uses Microstream to persist the greetings.
+ */
+package io.helidon.examples.integrations.microstream.greetings.mp;
diff --git a/examples/integrations/microstream/greetings-mp/src/main/resources/META-INF/beans.xml b/examples/integrations/microstream/greetings-mp/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..e149ced7d
--- /dev/null
+++ b/examples/integrations/microstream/greetings-mp/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/integrations/microstream/greetings-mp/src/main/resources/META-INF/microprofile-config.properties b/examples/integrations/microstream/greetings-mp/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 000000000..d14f5354e
--- /dev/null
+++ b/examples/integrations/microstream/greetings-mp/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+one.microstream.storage.greetings.storage-directory=./greetingsStorage
diff --git a/examples/integrations/microstream/greetings-mp/src/test/java/io/helidon/examples/integrations/microstream/greetings/mp/MicrostreamExampleGreetingsMpTest.java b/examples/integrations/microstream/greetings-mp/src/test/java/io/helidon/examples/integrations/microstream/greetings/mp/MicrostreamExampleGreetingsMpTest.java
new file mode 100644
index 000000000..bcae47416
--- /dev/null
+++ b/examples/integrations/microstream/greetings-mp/src/test/java/io/helidon/examples/integrations/microstream/greetings/mp/MicrostreamExampleGreetingsMpTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.microstream.greetings.mp;
+
+import java.nio.file.Path;
+
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.client.WebTarget;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@HelidonTest
+class MicrostreamExampleGreetingsMpTest {
+
+ @Inject
+ private WebTarget webTarget;
+
+ @TempDir
+ static Path tempDir;
+
+ @BeforeAll
+ static void beforeAll() {
+ System.setProperty("one.microstream.storage.greetings.storage-directory", tempDir.toString());
+ }
+
+ @Test
+ void testGreeting() {
+ GreetingMessage response = webTarget.path("/greet").request().get(GreetingMessage.class);
+
+ assertEquals("Hello World!", response.getMessage(), "response should be 'Hello World' ");
+ }
+
+}
diff --git a/examples/integrations/microstream/greetings-se/README.md b/examples/integrations/microstream/greetings-se/README.md
new file mode 100644
index 000000000..358ca8939
--- /dev/null
+++ b/examples/integrations/microstream/greetings-se/README.md
@@ -0,0 +1,32 @@
+# Microstream integration example
+
+This example uses Microstream to persist a log entry for every greeting
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-integrations-microstream-greetings-se.jar
+```
+
+## Endpoints
+
+Get default greeting message:
+```shell
+curl -X GET http://localhost:8080/greet
+```
+
+Get greeting message for Joe:
+```shell
+curl -X GET http://localhost:8080/greet/Joe
+```
+
+Change greeting:
+```shell
+curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting
+```
+
+Get the logs:
+```shell
+curl -X GET http://localhost:8080/greet/logs
+```
\ No newline at end of file
diff --git a/examples/integrations/microstream/greetings-se/pom.xml b/examples/integrations/microstream/greetings-se/pom.xml
new file mode 100644
index 000000000..e1ab07b07
--- /dev/null
+++ b/examples/integrations/microstream/greetings-se/pom.xml
@@ -0,0 +1,106 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+
+ helidon-examples-integrations-microstream-greetings-se
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration Microstream Greetings se
+
+
+ io.helidon.examples.integrations.microstream.greetings.se.Main
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.integrations.microstream
+ helidon-integrations-microstream
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe
+
+
+ io.helidon.http.media
+ helidon-http-media-jsonp
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.health
+ helidon-health-checks
+
+
+ io.helidon.metrics
+ helidon-metrics-api
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-metrics
+ runtime
+
+
+ io.helidon.metrics
+ helidon-metrics-system-meters
+ runtime
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/GreetingService.java b/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/GreetingService.java
new file mode 100644
index 000000000..809862139
--- /dev/null
+++ b/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/GreetingService.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.microstream.greetings.se;
+
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Logger;
+
+import io.helidon.config.Config;
+import io.helidon.http.Status;
+import io.helidon.integrations.microstream.core.EmbeddedStorageManagerBuilder;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+import jakarta.json.Json;
+import jakarta.json.JsonArrayBuilder;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+
+/**
+ * A simple service to greet you. Examples:
+ *
+ * Get default greeting message:
+ * curl -X GET http://localhost:8080/greet
+ *
+ * Get greeting message for Joe:
+ * curl -X GET http://localhost:8080/greet/Joe
+ *
+ * Change greeting
+ * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting
+ *
+ * Get the logs:
+ * curl -X GET http://localhost:8080/greet/logs
+ *
+ * The message is returned as a JSON object
+ */
+
+public class GreetingService implements HttpService {
+
+ private final AtomicReference greeting = new AtomicReference<>();
+ private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
+ private static final Logger LOGGER = Logger.getLogger(GreetingService.class.getName());
+
+ private final GreetingServiceMicrostreamContext mctx;
+
+ GreetingService(Config config) {
+ greeting.set(config.get("app.greeting").asString().orElse("Ciao"));
+
+ mctx = new GreetingServiceMicrostreamContext(EmbeddedStorageManagerBuilder.create(config.get("microstream")));
+ // we need to initialize the root element first
+ // if we do not wait here, we have a race where HTTP method may be invoked before we initialize root
+ mctx.start();
+ mctx.initRootElement();
+ }
+
+ @Override
+ public void routing(HttpRules rules) {
+ rules.get("/", this::getDefaultMessageHandler)
+ .get("/logs", this::getLog)
+ .get("/{name}", this::getMessageHandler)
+ .put("/greeting", this::updateGreetingHandler);
+ }
+
+ private void getLog(ServerRequest request, ServerResponse response) {
+ JsonArrayBuilder arrayBuilder = JSON.createArrayBuilder();
+ mctx.getLogs().forEach((entry) -> arrayBuilder.add(
+ JSON.createObjectBuilder()
+ .add("name", entry.getName())
+ .add("time", entry.getDateTime().toString())));
+ response.send(arrayBuilder.build());
+ }
+
+ /**
+ * Return a worldly greeting message.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void getDefaultMessageHandler(ServerRequest request,
+ ServerResponse response) {
+ sendResponse(response, "World");
+ }
+
+ /**
+ * Return a greeting message using the name that was provided.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void getMessageHandler(ServerRequest request,
+ ServerResponse response) {
+ String name = request.path().pathParameters().get("name");
+ sendResponse(response, name);
+ }
+
+ private void sendResponse(ServerResponse response, String name) {
+ String msg = String.format("%s %s!", greeting.get(), name);
+
+ mctx.addLogEntry(name);
+
+ JsonObject returnObject = JSON.createObjectBuilder()
+ .add("message", msg)
+ .build();
+ response.send(returnObject);
+ }
+
+ private void updateGreetingFromJson(JsonObject jo, ServerResponse response) {
+ if (!jo.containsKey("greeting")) {
+ JsonObject jsonErrorObject = JSON.createObjectBuilder()
+ .add("error", "No greeting provided")
+ .build();
+ response.status(Status.BAD_REQUEST_400)
+ .send(jsonErrorObject);
+ return;
+ }
+
+ greeting.set(jo.getString("greeting"));
+ response.status(Status.NO_CONTENT_204).send();
+ }
+
+ /**
+ * Set the greeting to use in future messages.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void updateGreetingHandler(ServerRequest request,
+ ServerResponse response) {
+ JsonObject jsonObject = request.content().as(JsonObject.class);
+ updateGreetingFromJson(jsonObject, response);
+ }
+
+}
diff --git a/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/GreetingServiceMicrostreamContext.java b/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/GreetingServiceMicrostreamContext.java
new file mode 100644
index 000000000..8edcedbd5
--- /dev/null
+++ b/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/GreetingServiceMicrostreamContext.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.microstream.greetings.se;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+import one.microstream.storage.embedded.types.EmbeddedStorageManager;
+
+/**
+ * This class extends {@link MicrostreamExecutionContext} and provides data access methods.
+ */
+public class GreetingServiceMicrostreamContext extends MicrostreamExecutionContext {
+
+ /**
+ * Create a new instance.
+ *
+ * @param storageManager the EmbeddedStorageManager used.
+ */
+ public GreetingServiceMicrostreamContext(EmbeddedStorageManager storageManager) {
+ super(storageManager);
+ }
+
+ /**
+ * Add and store a new log entry.
+ *
+ * @param name parameter for log text.
+ */
+ @SuppressWarnings({"unchecked", "resource"})
+ public void addLogEntry(String name) {
+ List logs = (List) storageManager().root();
+ logs.add(new LogEntry(name, LocalDateTime.now()));
+ storageManager().store(logs);
+ }
+
+ /**
+ * initialize the storage root with a new, empty List.
+ */
+ @SuppressWarnings("resource")
+ public void initRootElement() {
+ if (storageManager().root() == null) {
+ storageManager().setRoot(new ArrayList());
+ storageManager().storeRoot();
+ }
+ }
+
+ /**
+ * returns a List of all stored LogEntries.
+ *
+ * @return all LogEntries.
+ */
+ @SuppressWarnings({"unchecked", "resource"})
+ public List getLogs() {
+ return (List) storageManager().root();
+ }
+
+}
diff --git a/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/LogEntry.java b/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/LogEntry.java
new file mode 100644
index 000000000..83e67f942
--- /dev/null
+++ b/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/LogEntry.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.microstream.greetings.se;
+
+import java.time.LocalDateTime;
+
+/**
+ *
+ * simple POJO that represents a Log entry that is stored by microstream in this example.
+ *
+ */
+public class LogEntry {
+ private String name;
+ private LocalDateTime dateTime;
+
+ /**
+ * The Constructor.
+ *
+ * @param name name to be logged.
+ *
+ * @param dateTime dateTime date and time to be logged
+ */
+ public LogEntry(String name, LocalDateTime dateTime) {
+ super();
+ this.name = name;
+ this.dateTime = dateTime;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public LocalDateTime getDateTime() {
+ return dateTime;
+ }
+
+}
diff --git a/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/Main.java b/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/Main.java
new file mode 100644
index 000000000..8c03369a5
--- /dev/null
+++ b/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/Main.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.microstream.greetings.se;
+
+import io.helidon.config.ClasspathConfigSource;
+import io.helidon.config.Config;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.WebServerConfig;
+import io.helidon.webserver.http.HttpRouting;
+
+/**
+ * Microstream demo with a simple rest application.
+ */
+public class Main {
+
+ /**
+ * Cannot be instantiated.
+ */
+ private Main() {
+ }
+
+ /**
+ * Application main entry point.
+ *
+ * @param args command line arguments.
+ */
+ public static void main(String[] args) {
+ WebServerConfig.Builder builder = WebServer.builder();
+ setup(builder);
+ WebServer server = builder.build().start();
+ System.out.println("WEB server is up! http://localhost:" + server.port() + "/greet");
+ }
+
+ static void setup(WebServerConfig.Builder server) {
+ LogConfig.configureRuntime();
+ Config config = Config.builder()
+ .addSource(ClasspathConfigSource.create("/application.yaml"))
+ .build();
+
+ // Build server with JSONP support
+ server.config(config.get("server"))
+ .routing(r -> routing(r, config));
+ }
+
+ /**
+ * Setup routing.
+ *
+ * @param routing routing builder
+ * @param config configuration of this server
+ */
+ static void routing(HttpRouting.Builder routing, Config config) {
+ GreetingService greetService = new GreetingService(config);
+ routing.register("/greet", greetService);
+ }
+}
diff --git a/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/MicrostreamExecutionContext.java b/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/MicrostreamExecutionContext.java
new file mode 100644
index 000000000..4497cf1b5
--- /dev/null
+++ b/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/MicrostreamExecutionContext.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.microstream.greetings.se;
+
+import one.microstream.reference.LazyReferenceManager;
+import one.microstream.storage.embedded.types.EmbeddedStorageManager;
+
+/**
+ * Provides a very simply way to access a Microstream storage, and it's associated data.
+ */
+public class MicrostreamExecutionContext {
+
+ private final EmbeddedStorageManager storage;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param storageManager the used EmbeddedStorageManager.
+ */
+ public MicrostreamExecutionContext(EmbeddedStorageManager storageManager) {
+ this.storage = storageManager;
+ }
+
+ /**
+ * returns the used storageManager.
+ *
+ * @return the used EmbeddedStorageManager.
+ */
+ public EmbeddedStorageManager storageManager() {
+ return storage;
+ }
+
+ /**
+ * Start the storage.
+ *
+ * @return the started EmbeddedStorageManager.
+ */
+ public EmbeddedStorageManager start() {
+ return storage.start();
+ }
+
+ /**
+ * Shutdown the storage.
+ *
+ * @return the stopped EmbeddedStorageManager.
+ */
+ public EmbeddedStorageManager shutdown() {
+ storage.shutdown();
+ LazyReferenceManager.get().stop();
+ return storage;
+ }
+
+ /**
+ * Return the persistent object graph's root object.
+ *
+ * @param type of the root object
+ * @return the graph's root object casted to
+ */
+ @SuppressWarnings("unchecked")
+ public T root() {
+ return (T) storage.root();
+ }
+
+ /**
+ * Sets the passed instance as the new root for the persistent object graph.
+ *
+ * @param object the new root object
+ * @return the new root object
+ */
+ public Object setRoot(Object object) {
+ return storage.setRoot(object);
+ }
+
+ /**
+ * Stores the registered root instance.
+ *
+ * @return the root instance's objectId.
+ */
+ public long storeRoot() {
+ return storage.storeRoot();
+ }
+
+ /**
+ * Stores the passed object.
+ *
+ * @param object object to store
+ * @return the object id representing the passed instance.
+ */
+ public long store(Object object) {
+ return storage.store(object);
+ }
+}
diff --git a/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/package-info.java b/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/package-info.java
new file mode 100644
index 000000000..5fa972bf3
--- /dev/null
+++ b/examples/integrations/microstream/greetings-se/src/main/java/io/helidon/examples/integrations/microstream/greetings/se/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * An example that uses Microstream to persist a log entry for every greeting.
+ */
+package io.helidon.examples.integrations.microstream.greetings.se;
diff --git a/examples/integrations/microstream/greetings-se/src/main/resources/application.yaml b/examples/integrations/microstream/greetings-se/src/main/resources/application.yaml
new file mode 100644
index 000000000..d22a1bae2
--- /dev/null
+++ b/examples/integrations/microstream/greetings-se/src/main/resources/application.yaml
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+app:
+ greeting: "Hello"
+
+server:
+ port: 8080
+ host: 0.0.0.0
+
+microstream:
+ channel-count: 4
+ housekeeping-interval: 2000ms
diff --git a/examples/integrations/microstream/greetings-se/src/test/java/io/helidon/examples/integrations/microstream/greetings/se/MicrostreamExampleGreetingsSeTest.java b/examples/integrations/microstream/greetings-se/src/test/java/io/helidon/examples/integrations/microstream/greetings/se/MicrostreamExampleGreetingsSeTest.java
new file mode 100644
index 000000000..caebeb120
--- /dev/null
+++ b/examples/integrations/microstream/greetings-se/src/test/java/io/helidon/examples/integrations/microstream/greetings/se/MicrostreamExampleGreetingsSeTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.microstream.greetings.se;
+
+import java.nio.file.Path;
+
+import io.helidon.webserver.testing.junit5.ServerTest;
+import io.helidon.webserver.testing.junit5.SetUpServer;
+import io.helidon.webclient.http1.Http1Client;
+import io.helidon.webclient.http1.Http1ClientResponse;
+import io.helidon.webserver.WebServerConfig;
+
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@ServerTest
+public class MicrostreamExampleGreetingsSeTest {
+
+ @TempDir
+ static Path tempDir;
+
+ private final Http1Client client;
+
+ public MicrostreamExampleGreetingsSeTest(Http1Client client) {
+ this.client = client;
+ }
+
+ @SetUpServer
+ static void setup(WebServerConfig.Builder server) {
+ System.setProperty("microstream.storage-directory", tempDir.toString());
+ Main.setup(server);
+ }
+
+ @Test
+ void testExample() {
+ try (Http1ClientResponse response = client.get("/greet/Joe").request()) {
+ assertThat(response.as(JsonObject.class).getString("message"), is("Hello Joe!"));
+ }
+
+ try (Http1ClientResponse response = client.get("/greet/logs").request()) {
+ JsonArray jsonArray = response.as(JsonArray.class);
+ assertThat(jsonArray.get(0).asJsonObject().getString("name"), is("Joe"));
+ assertThat(jsonArray.get(0).asJsonObject().getString("time"), notNullValue());
+ }
+ }
+}
diff --git a/examples/integrations/microstream/pom.xml b/examples/integrations/microstream/pom.xml
new file mode 100644
index 000000000..ee19232c2
--- /dev/null
+++ b/examples/integrations/microstream/pom.xml
@@ -0,0 +1,39 @@
+
+
+
+ 4.0.0
+
+ io.helidon.examples.integrations
+ helidon-examples-integrations-project
+ 1.0.0-SNAPSHOT
+
+
+ io.helidon.examples.integrations.microstream
+ helidon-examples-integrations-microstream-project
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration Microstreams
+ pom
+
+
+ greetings-se
+ greetings-mp
+
+
\ No newline at end of file
diff --git a/examples/integrations/neo4j/README.md b/examples/integrations/neo4j/README.md
new file mode 100644
index 000000000..887e2f240
--- /dev/null
+++ b/examples/integrations/neo4j/README.md
@@ -0,0 +1,40 @@
+# Helidon SE integration with Neo4J example
+
+## Build and run
+
+Bring up a Neo4j instance via Docker
+
+```shell
+docker run --publish=7474:7474 --publish=7687:7687 -e 'NEO4J_AUTH=neo4j/secret' neo4j:4.0
+```
+
+Goto the Neo4j browser and play the first step of the movies graph: [`:play movies`](http://localhost:7474/browser/?cmd=play&arg=movies).
+
+Build and run with JDK20
+```shell
+mvn package
+java -jar target/helidon-examples-integration-neo4j.jar
+```
+
+Then access the rest API like this:
+
+````shell
+export PORT=38837
+curl localhost:${PORT}/api/movies
+````
+
+# Health and metrics
+
+Neo4jSupport provides health checks and metrics reading from Neo4j.
+
+Enable them in the driver:
+```yaml
+ pool:
+ metricsEnabled: true
+```
+
+```shell
+export PORT=38837
+curl localhost:${PORT}/observe/health
+curl localhost:${PORT}/observe/metrics
+```
diff --git a/examples/integrations/neo4j/pom.xml b/examples/integrations/neo4j/pom.xml
new file mode 100644
index 000000000..9ca3792c5
--- /dev/null
+++ b/examples/integrations/neo4j/pom.xml
@@ -0,0 +1,133 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ helidon-examples-integration-neo4j
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integrations Neo4j
+
+
+ io.helidon.examples.integrations.neo4j.Main
+ 5.12.0
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.health
+ helidon-health-checks
+
+
+ io.helidon.http.media
+ helidon-http-media-jsonp
+
+
+ io.helidon.http.media
+ helidon-http-media-jsonb
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-health
+
+
+ io.helidon.webserver.observe
+ helidon-webserver-observe-metrics
+
+
+ io.helidon.metrics
+ helidon-metrics
+
+
+ io.helidon.integrations.neo4j
+ helidon-integrations-neo4j
+
+
+ io.helidon.integrations.neo4j
+ helidon-integrations-neo4j-health
+
+
+ io.helidon.integrations.neo4j
+ helidon-integrations-neo4j-metrics
+
+
+ io.helidon.logging
+ helidon-logging-jul
+ runtime
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
+ org.neo4j.test
+ neo4j-harness
+ ${neo4j-harness.version}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+
+
diff --git a/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/Main.java b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/Main.java
new file mode 100644
index 000000000..03c47d55d
--- /dev/null
+++ b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/Main.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.neo4j;
+
+import java.util.List;
+
+import io.helidon.config.Config;
+import io.helidon.examples.integrations.neo4j.domain.MovieRepository;
+import io.helidon.health.checks.DeadlockHealthCheck;
+import io.helidon.health.checks.DiskSpaceHealthCheck;
+import io.helidon.health.checks.HeapMemoryHealthCheck;
+import io.helidon.integrations.neo4j.Neo4j;
+import io.helidon.integrations.neo4j.health.Neo4jHealthCheck;
+import io.helidon.integrations.neo4j.metrics.Neo4jMetricsSupport;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.observe.ObserveFeature;
+import io.helidon.webserver.observe.health.HealthObserver;
+import io.helidon.webserver.spi.ServerFeature;
+
+import org.neo4j.driver.Driver;
+
+import static io.helidon.webserver.http.HttpRouting.Builder;
+
+/**
+ * The application main class.
+ */
+public class Main {
+ /**
+ * Cannot be instantiated.
+ */
+ private Main() {
+ }
+
+ /**
+ * Application main entry point.
+ *
+ * @param args command line arguments.
+ */
+ public static void main(String[] args) {
+ // load logging configuration
+ LogConfig.configureRuntime();
+
+ startServer();
+ }
+
+ static void startServer() {
+ Neo4j neo4j = Neo4j.create(Config.create().get("neo4j"));
+ Driver neo4jDriver = neo4j.driver();
+
+ WebServer server = WebServer.builder()
+ .featuresDiscoverServices(false)
+ .features(features(neo4jDriver))
+ .routing(it -> routing(it, neo4jDriver))
+ .build()
+ .start();
+
+ System.out.println("WEB server is up! http://localhost:" + server.port() + "/api/movies");
+ }
+
+ static List features(Driver neo4jDriver) {
+ Neo4jHealthCheck healthCheck = Neo4jHealthCheck.create(neo4jDriver);
+ return List.of(ObserveFeature.just(HealthObserver.builder()
+ .useSystemServices(false)
+ .addCheck(HeapMemoryHealthCheck.create())
+ .addCheck(DiskSpaceHealthCheck.create())
+ .addCheck(DeadlockHealthCheck.create())
+ .addCheck(healthCheck)
+ .build()));
+ }
+
+ /**
+ * Updates HTTP Routing.
+ */
+ static void routing(Builder routing, Driver neo4jDriver) {
+
+
+ Neo4jMetricsSupport.builder()
+ .driver(neo4jDriver)
+ .build()
+ .initialize();
+
+
+ MovieService movieService = new MovieService(new MovieRepository(neo4jDriver));
+
+
+
+ routing.register(movieService);
+ }
+}
+
diff --git a/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/MovieService.java b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/MovieService.java
new file mode 100644
index 000000000..5297b0e27
--- /dev/null
+++ b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/MovieService.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.neo4j;
+
+import io.helidon.examples.integrations.neo4j.domain.MovieRepository;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+/**
+ * The Movie service.
+ */
+public class MovieService implements HttpService {
+
+ private final MovieRepository movieRepository;
+
+ /**
+ * The movies service.
+ *
+ * @param movieRepository a movie repository.
+ */
+ public MovieService(MovieRepository movieRepository) {
+ this.movieRepository = movieRepository;
+ }
+
+ @Override
+ public void routing(HttpRules rules) {
+ rules.get("/api/movies", this::findMoviesHandler);
+ }
+
+ private void findMoviesHandler(ServerRequest request, ServerResponse response) {
+ response.send(this.movieRepository.findAll());
+ }
+}
diff --git a/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/Actor.java b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/Actor.java
new file mode 100644
index 000000000..8de20701c
--- /dev/null
+++ b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/Actor.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2002-2020 "Neo4j,"
+ * Neo4j Sweden AB [http://neo4j.com]
+ * This file is part of Neo4j.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.helidon.examples.integrations.neo4j.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/*
+ * Helidon changes are under the copyright of:
+ *
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * The Actor class.
+ *
+ * @author Michael Simons
+ */
+public class Actor {
+
+ private final String name;
+
+ private final List roles;
+
+ /**
+ * Constructor for actor.
+ *
+ * @param name
+ * @param roles
+ */
+ public Actor(String name, final List roles) {
+ this.name = name;
+ this.roles = new ArrayList<>(roles);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+}
diff --git a/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/Movie.java b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/Movie.java
new file mode 100644
index 000000000..a091f41ef
--- /dev/null
+++ b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/Movie.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2002-2020 "Neo4j,"
+ * Neo4j Sweden AB [http://neo4j.com]
+ * This file is part of Neo4j.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.helidon.examples.integrations.neo4j.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/*
+ * Helidon changes are under the copyright of:
+ *
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * The Movie class.
+ *
+ * @author Michael Simons
+ */
+public class Movie {
+
+ private final String title;
+
+ private final String description;
+
+ private List actors = new ArrayList<>();
+
+ private List directors = new ArrayList<>();
+
+ private Integer released;
+
+ /**
+ * Constructor for Movie.
+ *
+ * @param title
+ * @param description
+ */
+ public Movie(String title, String description) {
+ this.title = title;
+ this.description = description;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public List getActors() {
+ return actors;
+ }
+
+ public void setActors(List actors) {
+ this.actors = actors;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public List getDirectors() {
+ return directors;
+ }
+
+ public void setDirectorss(List directors) {
+ this.directors = directors;
+ }
+
+ public Integer getReleased() {
+ return released;
+ }
+
+ public void setReleased(Integer released) {
+ this.released = released;
+ }
+}
diff --git a/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/MovieRepository.java b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/MovieRepository.java
new file mode 100644
index 000000000..c7b6e3bcf
--- /dev/null
+++ b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/MovieRepository.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2002-2020 "Neo4j,"
+ * Neo4j Sweden AB [http://neo4j.com]
+ * This file is part of Neo4j.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.helidon.examples.integrations.neo4j.domain;
+
+import java.util.List;
+
+import org.neo4j.driver.Driver;
+import org.neo4j.driver.Value;
+
+/*
+ * Helidon changes are under the copyright of:
+ *
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * The Movie repository.
+ *
+ * @author Michael Simons
+ */
+public final class MovieRepository {
+
+ private final Driver driver;
+
+ /**
+ * Constructor for the repo.
+ *
+ * @param driver
+ */
+ public MovieRepository(Driver driver) {
+ this.driver = driver;
+ }
+
+ /**
+ * Returns all the movies.
+ * @return List with movies
+ */
+ public List findAll(){
+
+ try (var session = driver.session()) {
+
+ var query = ""
+ + "match (m:Movie) "
+ + "match (m) <- [:DIRECTED] - (d:Person) "
+ + "match (m) <- [r:ACTED_IN] - (a:Person) "
+ + "return m, collect(d) as directors, collect({name:a.name, roles: r.roles}) as actors";
+
+ return session.executeRead(tx -> tx.run(query).list(r -> {
+ var movieNode = r.get("m").asNode();
+
+ var directors = r.get("directors").asList(v -> {
+ var personNode = v.asNode();
+ return new Person(personNode.get("born").asInt(), personNode.get("name").asString());
+ });
+
+ var actors = r.get("actors").asList(v -> {
+ return new Actor(v.get("name").asString(), v.get("roles").asList(Value::asString));
+ });
+
+ var m = new Movie(movieNode.get("title").asString(), movieNode.get("tagline").asString());
+ m.setReleased(movieNode.get("released").asInt());
+ m.setDirectorss(directors);
+ m.setActors(actors);
+ return m;
+ }));
+ }
+ }
+}
diff --git a/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/Person.java b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/Person.java
new file mode 100644
index 000000000..a2db40933
--- /dev/null
+++ b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/Person.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2002-2020 "Neo4j,"
+ * Neo4j Sweden AB [http://neo4j.com]
+ * This file is part of Neo4j.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.helidon.examples.integrations.neo4j.domain;
+
+/*
+ * Helidon changes are under the copyright of:
+ *
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * The Person class.
+ *
+ * @author Michael Simons
+ */
+public class Person {
+
+ private final String name;
+
+ private Integer born;
+
+ /**
+ * Constrictor for person.
+ * @param born
+ * @param name
+ */
+ public Person(Integer born, String name) {
+ this.born = born;
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Integer getBorn() {
+ return born;
+ }
+
+ public void setBorn(Integer born) {
+ this.born = born;
+ }
+
+ @SuppressWarnings("checkstyle:OperatorWrap")
+ @Override
+ public String toString() {
+ return "Person{" +
+ "name='" + name + '\'' +
+ ", born=" + born +
+ '}';
+ }
+}
diff --git a/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/package-info.java b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/package-info.java
new file mode 100644
index 000000000..0c5b784ac
--- /dev/null
+++ b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/domain/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2023, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Domain objects for movies.
+ */
+package io.helidon.examples.integrations.neo4j.domain;
diff --git a/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/package-info.java b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/package-info.java
new file mode 100644
index 000000000..40e08b886
--- /dev/null
+++ b/examples/integrations/neo4j/src/main/java/io/helidon/examples/integrations/neo4j/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Helidon Integrations Neo4j Example.
+ *
+ *
+ * @see io.helidon.examples.integrations.neo4j.Main
+ */
+package io.helidon.examples.integrations.neo4j;
diff --git a/examples/integrations/neo4j/src/main/resources/application.yaml b/examples/integrations/neo4j/src/main/resources/application.yaml
new file mode 100644
index 000000000..74153354d
--- /dev/null
+++ b/examples/integrations/neo4j/src/main/resources/application.yaml
@@ -0,0 +1,29 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+server:
+ port: 8080
+ host: 0.0.0.0
+
+
+neo4j:
+ uri: bolt://localhost:7687
+ authentication:
+ username: neo4j
+ password: secret
+ pool:
+ metricsEnabled: true
diff --git a/examples/integrations/neo4j/src/main/resources/logging.properties b/examples/integrations/neo4j/src/main/resources/logging.properties
new file mode 100644
index 000000000..c916a0505
--- /dev/null
+++ b/examples/integrations/neo4j/src/main/resources/logging.properties
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+# Component specific log levels
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
diff --git a/examples/integrations/neo4j/src/test/java/io/helidon/examples/integrations/neo4j/MainTest.java b/examples/integrations/neo4j/src/test/java/io/helidon/examples/integrations/neo4j/MainTest.java
new file mode 100644
index 000000000..1ee1f61dd
--- /dev/null
+++ b/examples/integrations/neo4j/src/test/java/io/helidon/examples/integrations/neo4j/MainTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.neo4j;
+
+import io.helidon.http.Status;
+import io.helidon.webclient.http1.Http1Client;
+import io.helidon.webclient.http1.Http1ClientResponse;
+import io.helidon.webserver.WebServerConfig;
+import io.helidon.webserver.testing.junit5.ServerTest;
+import io.helidon.webserver.testing.junit5.SetUpServer;
+
+import jakarta.json.JsonArray;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.neo4j.driver.Config;
+import org.neo4j.driver.Driver;
+import org.neo4j.driver.GraphDatabase;
+import org.neo4j.harness.Neo4j;
+import org.neo4j.harness.Neo4jBuilders;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Main test class for Neo4j Helidon SE application.
+ */
+@ServerTest
+public class MainTest {
+
+ static final String FIXTURE = """
+ CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})
+ CREATE (Keanu:Person {name:'Keanu Reeves', born:1964})
+ CREATE (Carrie:Person {name:'Carrie-Anne Moss', born:1967})
+ CREATE (Laurence:Person {name:'Laurence Fishburne', born:1961})
+ CREATE (Hugo:Person {name:'Hugo Weaving', born:1960})
+ CREATE (LillyW:Person {name:'Lilly Wachowski', born:1967})
+ CREATE (LanaW:Person {name:'Lana Wachowski', born:1965})
+ CREATE (JoelS:Person {name:'Joel Silver', born:1952})
+ CREATE (KevinB:Person {name:'Kevin Bacon', born:1958})
+ CREATE
+ (Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrix),
+ (Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrix),
+ (Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrix),
+ (Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrix),
+ (LillyW)-[:DIRECTED]->(TheMatrix),
+ (LanaW)-[:DIRECTED]->(TheMatrix),
+ (JoelS)-[:PRODUCED]->(TheMatrix)
+
+ CREATE (Emil:Person {name:"Emil Eifrem", born:1978})
+ CREATE (Emil)-[:ACTED_IN {roles:["Emil"]}]->(TheMatrix)
+
+ CREATE (TheMatrixReloaded:Movie {title:'The Matrix Reloaded', released:2003, tagline:'Free your mind'})
+ CREATE
+ (Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrixReloaded),
+ (Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrixReloaded),
+ (Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrixReloaded),
+ (Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrixReloaded),
+ (LillyW)-[:DIRECTED]->(TheMatrixReloaded),
+ (LanaW)-[:DIRECTED]->(TheMatrixReloaded),
+ (JoelS)-[:PRODUCED]->(TheMatrixReloaded)
+
+ CREATE (TheMatrixRevolutions:Movie {title:'The Matrix Revolutions', released:2003,
+ tagline:'Everything that has a beginning has an end'})
+ CREATE
+ (Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrixRevolutions),
+ (Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrixRevolutions),
+ (Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrixRevolutions),
+ (KevinB)-[:ACTED_IN {roles:['Unknown']}]->(TheMatrixRevolutions),
+ (Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrixRevolutions),
+ (LillyW)-[:DIRECTED]->(TheMatrixRevolutions),
+ (LanaW)-[:DIRECTED]->(TheMatrixRevolutions),
+ (JoelS)-[:PRODUCED]->(TheMatrixRevolutions)""";
+ private static Neo4j embeddedDatabaseServer;
+ private final Http1Client webClient;
+
+ public MainTest(Http1Client webClient) {
+ this.webClient = webClient;
+ }
+
+ @SetUpServer
+ static void server(WebServerConfig.Builder server) {
+ //Setup embedded Neo4j Server and inject in routing
+ embeddedDatabaseServer = Neo4jBuilders.newInProcessBuilder()
+ .withDisabledServer()
+ .withFixture(FIXTURE)
+ .build();
+
+ Driver driver = GraphDatabase.driver(embeddedDatabaseServer.boltURI(), Config.builder()
+ .withDriverMetrics()
+ .build());
+ server.features(Main.features(driver))
+ .routing(it -> Main.routing(it, driver));
+
+ }
+
+ @BeforeAll
+ static void startServer() {
+ //Setup embedded Neo4j Server and inject in routing
+ embeddedDatabaseServer = Neo4jBuilders.newInProcessBuilder()
+ .withDisabledServer()
+ .withFixture(FIXTURE)
+ .build();
+
+ System.setProperty("neo4j.uri", embeddedDatabaseServer.boltURI().toString());
+ }
+
+ @AfterAll
+ static void stopServer() {
+ if (embeddedDatabaseServer != null) {
+ embeddedDatabaseServer.close();
+ }
+ }
+
+ @Test
+ public void testHealth() {
+ try (Http1ClientResponse response = webClient.get("/observe/health").request()) {
+ assertThat(response.status(), is(Status.NO_CONTENT_204));
+ }
+ }
+
+ @Test
+ void testMovies() {
+ JsonArray result = webClient.get("/api/movies").requestEntity(JsonArray.class);
+ assertThat(result.getJsonObject(0).getString("title"), containsString("The Matrix"));
+ }
+}
\ No newline at end of file
diff --git a/examples/integrations/neo4j/src/test/java/io/helidon/examples/integrations/neo4j/package-info.java b/examples/integrations/neo4j/src/test/java/io/helidon/examples/integrations/neo4j/package-info.java
new file mode 100644
index 000000000..c52e32679
--- /dev/null
+++ b/examples/integrations/neo4j/src/test/java/io/helidon/examples/integrations/neo4j/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Tests for Helidon Integrations Neo4j Example.
+ */
+package io.helidon.examples.integrations.neo4j;
diff --git a/examples/integrations/oci/README.md b/examples/integrations/oci/README.md
new file mode 100644
index 000000000..fe566dd16
--- /dev/null
+++ b/examples/integrations/oci/README.md
@@ -0,0 +1,5 @@
+# OCI Java SDK Examples
+
+```shell
+mvn package
+```
diff --git a/examples/integrations/oci/atp-cdi/README.md b/examples/integrations/oci/atp-cdi/README.md
new file mode 100644
index 000000000..2af418749
--- /dev/null
+++ b/examples/integrations/oci/atp-cdi/README.md
@@ -0,0 +1,28 @@
+# Helidon ATP MP Examples
+
+This example demonstrates how user can easily retrieve wallet from their ATP instance running in OCI and use information from that wallet to setup DataSource to do Database operations.
+
+It requires a running OCI ATP instance.
+
+Before running the test, make sure to update required properties in `application.yaml`
+
+- oci.atp.ocid: This is OCID of your running ATP instance.
+- oci.atp.walletPassword: password to encrypt the keys inside the wallet. The password must be at least 8 characters long and must include at least 1 letter and either 1 numeric character or 1 special character.
+- oracle.ucp.jdbc.PoolDataSource.atp.tnsNetServiceName: netServiceName of your database running inside OCI ATP as can be found in `tnsnames.ora` file.
+- oracle.ucp.jdbc.PoolDataSource.atp.user: User to access your database running inside OCI ATP.
+- oracle.ucp.jdbc.PoolDataSource.atp.password: Password of user to access your database running inside OCI ATP.
+
+Once you have updated required properties, you can run the example:
+
+```shell
+mvn package
+java -jar ./target/helidon-examples-integrations-oci-atp-cdi.jar
+```
+
+To verify that, you can retrieve wallet and do database operation:
+
+```shell
+curl http://localhost:8080/atp/wallet
+```
+
+You should see `Hello world!!`
\ No newline at end of file
diff --git a/examples/integrations/oci/atp-cdi/pom.xml b/examples/integrations/oci/atp-cdi/pom.xml
new file mode 100644
index 000000000..89b421456
--- /dev/null
+++ b/examples/integrations/oci/atp-cdi/pom.xml
@@ -0,0 +1,95 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+
+ io.helidon.examples.integrations.oci
+ helidon-examples-integrations-oci-atp-cdi
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration OCI ATP CDI
+ CDI integration with OCI ATP.
+
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-datasource-ucp
+
+
+ io.helidon.integrations.db
+ ojdbc
+
+
+ com.oracle.database.jdbc
+ ucp
+
+
+ io.helidon.integrations.oci.sdk
+ helidon-integrations-oci-sdk-cdi
+ runtime
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-database
+
+
+ io.helidon.config
+ helidon-config-yaml-mp
+
+
+ io.smallrye
+ jandex
+ runtime
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
diff --git a/examples/integrations/oci/atp-cdi/src/main/java/io/helidon/examples/integrations/oci/atp/cdi/AtpResource.java b/examples/integrations/oci/atp-cdi/src/main/java/io/helidon/examples/integrations/oci/atp/cdi/AtpResource.java
new file mode 100644
index 000000000..cedb2bf6a
--- /dev/null
+++ b/examples/integrations/oci/atp-cdi/src/main/java/io/helidon/examples/integrations/oci/atp/cdi/AtpResource.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.oci.atp.cdi;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyStore;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.oracle.bmc.database.Database;
+import com.oracle.bmc.database.model.GenerateAutonomousDatabaseWalletDetails;
+import com.oracle.bmc.database.requests.GenerateAutonomousDatabaseWalletRequest;
+import com.oracle.bmc.database.responses.GenerateAutonomousDatabaseWalletResponse;
+import com.oracle.bmc.http.client.Options;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Response;
+import oracle.security.pki.OraclePKIProvider;
+import oracle.ucp.jdbc.PoolDataSource;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+/**
+ * JAX-RS resource - REST API for the atp example.
+ */
+@Path("/atp")
+public class AtpResource {
+ private static final Logger LOGGER = Logger.getLogger(AtpResource.class.getName());
+
+ private final Database databaseClient;
+ private final PoolDataSource atpDataSource;
+ private final String atpTnsNetServiceName;
+
+ private final String atpOcid;
+ private final String walletPassword;
+
+ @Inject
+ AtpResource(Database databaseClient, @Named("atp") PoolDataSource atpDataSource,
+ @ConfigProperty(name = "oracle.ucp.jdbc.PoolDataSource.atp.tnsNetServiceName") String atpTnsNetServiceName,
+ @ConfigProperty(name = "oci.atp.ocid") String atpOcid,
+ @ConfigProperty(name = "oci.atp.walletPassword") String walletPassword) {
+ this.databaseClient = databaseClient;
+ this.atpDataSource = Objects.requireNonNull(atpDataSource);
+ this.atpTnsNetServiceName = atpTnsNetServiceName;
+ this.atpOcid = atpOcid;
+ this.walletPassword = walletPassword;
+ }
+
+ /**
+ * Generate wallet file for the configured ATP.
+ *
+ * @return response containing wallet file
+ */
+ @GET
+ @Path("/wallet")
+ public Response generateWallet() {
+ Options.shouldAutoCloseResponseInputStream(false);
+ GenerateAutonomousDatabaseWalletResponse walletResponse =
+ databaseClient.generateAutonomousDatabaseWallet(
+ GenerateAutonomousDatabaseWalletRequest.builder()
+ .autonomousDatabaseId(this.atpOcid)
+ .generateAutonomousDatabaseWalletDetails(
+ GenerateAutonomousDatabaseWalletDetails.builder()
+ .password(this.walletPassword)
+ .build())
+ .build());
+
+ if (walletResponse.getContentLength() == 0) {
+ LOGGER.log(Level.SEVERE, "GenerateAutonomousDatabaseWalletResponse is empty");
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+
+ byte[] walletContent = null;
+ try {
+ walletContent = walletResponse.getInputStream().readAllBytes();
+ } catch (IOException e) {
+ LOGGER.log(Level.SEVERE, "Error processing GenerateAutonomousDatabaseWalletResponse", e);
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ }
+ String returnEntity = null;
+ try {
+ this.atpDataSource.setSSLContext(getSSLContext(walletContent));
+ this.atpDataSource.setURL(getJdbcUrl(walletContent, this.atpTnsNetServiceName));
+ try (
+ Connection connection = this.atpDataSource.getConnection();
+ PreparedStatement ps = connection.prepareStatement("SELECT 'Hello world!!' FROM DUAL");
+ ResultSet rs = ps.executeQuery()
+ ){
+ rs.next();
+ returnEntity = rs.getString(1);
+ }
+ } catch (SQLException e) {
+ LOGGER.log(Level.SEVERE, "Error setting up DataSource", e);
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ }
+
+ return Response.status(Response.Status.OK).entity(returnEntity).build();
+ }
+
+ /**
+ * Returns SSLContext based on cwallet.sso in wallet.
+ *
+ * @param walletContent
+ * @return SSLContext
+ */
+ private static SSLContext getSSLContext(byte[] walletContent) throws IllegalStateException {
+ SSLContext sslContext = null;
+ try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new ByteArrayInputStream(walletContent)))) {
+ ZipEntry entry = null;
+ while ((entry = zis.getNextEntry()) != null) {
+ if (entry.getName().equals("cwallet.sso")) {
+ KeyStore keyStore = KeyStore.getInstance("SSO", new OraclePKIProvider());
+ keyStore.load(zis, null);
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX");
+ trustManagerFactory.init(keyStore);
+ keyManagerFactory.init(keyStore, null);
+ sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
+ }
+ zis.closeEntry();
+ }
+ } catch (RuntimeException | Error throwMe) {
+ throw throwMe;
+ } catch (Exception e) {
+ throw new IllegalStateException("Error while getting SSLContext from wallet.", e);
+ }
+ return sslContext;
+ }
+
+ /**
+ * Returns JDBC URL with connection description for the given service based on tnsnames.ora in wallet.
+ *
+ * @param walletContent
+ * @param tnsNetServiceName
+ * @return String
+ */
+ private static String getJdbcUrl(byte[] walletContent, String tnsNetServiceName) throws IllegalStateException {
+ String jdbcUrl = null;
+ try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new ByteArrayInputStream(walletContent)))) {
+ ZipEntry entry = null;
+ while ((entry = zis.getNextEntry()) != null) {
+ if (entry.getName().equals("tnsnames.ora")) {
+ jdbcUrl = new String(zis.readAllBytes(), StandardCharsets.UTF_8)
+ .replaceFirst(tnsNetServiceName + "\\s*=\\s*", "jdbc:oracle:thin:@")
+ .replaceAll("\\n[^\\n]+", "");
+ }
+ zis.closeEntry();
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Error while getting JDBC URL from wallet.", e);
+ }
+ return jdbcUrl;
+ }
+}
+
diff --git a/examples/integrations/oci/atp-cdi/src/main/java/io/helidon/examples/integrations/oci/atp/cdi/package-info.java b/examples/integrations/oci/atp-cdi/src/main/java/io/helidon/examples/integrations/oci/atp/cdi/package-info.java
new file mode 100644
index 000000000..93ab02108
--- /dev/null
+++ b/examples/integrations/oci/atp-cdi/src/main/java/io/helidon/examples/integrations/oci/atp/cdi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example of integration with OCI ATP in CDI application.
+ */
+package io.helidon.examples.integrations.oci.atp.cdi;
diff --git a/examples/integrations/oci/atp-cdi/src/main/resources/META-INF/beans.xml b/examples/integrations/oci/atp-cdi/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..e149ced7d
--- /dev/null
+++ b/examples/integrations/oci/atp-cdi/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/integrations/oci/atp-cdi/src/main/resources/application.yaml b/examples/integrations/oci/atp-cdi/src/main/resources/application.yaml
new file mode 100644
index 000000000..d7073eb23
--- /dev/null
+++ b/examples/integrations/oci/atp-cdi/src/main/resources/application.yaml
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# The values are read from
+# ~/helidon/conf/examples.yaml
+# or you can just update them here
+
+server:
+ port: 8080
+
+oracle:
+ ucp:
+ jdbc:
+ PoolDataSource:
+ atp:
+ connectionFactoryClassName: oracle.jdbc.pool.OracleDataSource
+ tnsNetServiceName: "${atp.db.tnsNetServiceName}"
+ user: "${atp.db.user}"
+ password: "${atp.db.password}"
+
+oci:
+ atp:
+ ocid: "${oci.properties.atp-ocid}"
+ walletPassword: "${oci.properties.atp-walletPassword}"
\ No newline at end of file
diff --git a/examples/integrations/oci/atp-cdi/src/main/resources/logging.properties b/examples/integrations/oci/atp-cdi/src/main/resources/logging.properties
new file mode 100644
index 000000000..40dc82ff1
--- /dev/null
+++ b/examples/integrations/oci/atp-cdi/src/main/resources/logging.properties
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+io.helidon.level=INFO
+io.helidon.integrations.level=INFO
+io.helidon.webclient.level=INFO
diff --git a/examples/integrations/oci/atp/README.md b/examples/integrations/oci/atp/README.md
new file mode 100644
index 000000000..da82d2aa9
--- /dev/null
+++ b/examples/integrations/oci/atp/README.md
@@ -0,0 +1,28 @@
+# Helidon ATP SE Examples
+
+This example demonstrates how user can easily retrieve wallet from their ATP instance running in OCI and use information from that wallet to setup DataSource to do Database operations.
+
+It requires a running OCI ATP instance.
+
+Before running the test, make sure to update required properties in `application.yaml`
+
+- oci.atp.ocid: This is OCID of your running ATP instance.
+- oci.atp.walletPassword: password to encrypt the keys inside the wallet. The password must be at least 8 characters long and must include at least 1 letter and either 1 numeric character or 1 special character.
+- db.tnsNetServiceName: netServiceName of your database running inside OCI ATP as can be found in `tnsnames.ora` file.
+- db.userName: User to access your database running inside OCI ATP.
+- db.password: Password of user to access your database running inside OCI ATP.
+
+Once you have updated required properties, you can run the example:
+
+```shell
+mvn package
+java -jar ./target/helidon-examples-integrations-oci-atp.jar
+```
+
+To verify that, you can retrieve wallet and do database operation:
+
+```shell
+curl http://localhost:8080/atp/wallet
+```
+
+You should see `Hello world!!`
diff --git a/examples/integrations/oci/atp/pom.xml b/examples/integrations/oci/atp/pom.xml
new file mode 100644
index 000000000..0e3a3c339
--- /dev/null
+++ b/examples/integrations/oci/atp/pom.xml
@@ -0,0 +1,89 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+
+ io.helidon.examples.integrations.oci
+ helidon-examples-integrations-oci-atp
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration OCI ATP
+ Integration with OCI ATP.
+
+
+ io.helidon.examples.integrations.oci.atp.OciAtpMain
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.dbclient
+ helidon-dbclient
+
+
+ io.helidon.dbclient
+ helidon-dbclient-jdbc
+
+
+ io.helidon.integrations.db
+ ojdbc
+
+
+ com.oracle.database.jdbc
+ ucp
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-database
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-common-httpclient-jersey3
+ runtime
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/integrations/oci/atp/src/main/java/io/helidon/examples/integrations/oci/atp/AtpService.java b/examples/integrations/oci/atp/src/main/java/io/helidon/examples/integrations/oci/atp/AtpService.java
new file mode 100644
index 000000000..125e9f437
--- /dev/null
+++ b/examples/integrations/oci/atp/src/main/java/io/helidon/examples/integrations/oci/atp/AtpService.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.oci.atp;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyStore;
+import java.sql.SQLException;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+
+import io.helidon.config.Config;
+import io.helidon.dbclient.DbClient;
+import io.helidon.dbclient.DbRow;
+import io.helidon.dbclient.jdbc.JdbcClientProvider;
+import io.helidon.http.Status;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+import com.oracle.bmc.database.Database;
+import com.oracle.bmc.database.model.GenerateAutonomousDatabaseWalletDetails;
+import com.oracle.bmc.database.requests.GenerateAutonomousDatabaseWalletRequest;
+import com.oracle.bmc.database.responses.GenerateAutonomousDatabaseWalletResponse;
+import oracle.jdbc.pool.OracleDataSource;
+import oracle.security.pki.OraclePKIProvider;
+import oracle.ucp.jdbc.PoolDataSource;
+import oracle.ucp.jdbc.PoolDataSourceFactory;
+
+class AtpService implements HttpService {
+ private static final Logger LOGGER = Logger.getLogger(AtpService.class.getName());
+
+ private final Database databaseClient;
+ private final Config config;
+
+ AtpService(Database databaseClient, Config config) {
+ this.databaseClient = databaseClient;
+ this.config = config;
+ }
+
+ @Override
+ public void routing(HttpRules rules) {
+ rules.get("/wallet", this::generateWallet);
+ }
+
+ /**
+ * Generate wallet file for the configured ATP.
+ */
+ private void generateWallet(ServerRequest req, ServerResponse res) {
+ String ocid = config.get("oci.atp.ocid").asString().get();
+ GenerateAutonomousDatabaseWalletDetails walletDetails =
+ GenerateAutonomousDatabaseWalletDetails.builder()
+ .password(ocid)
+ .build();
+ GenerateAutonomousDatabaseWalletResponse walletResponse = databaseClient
+ .generateAutonomousDatabaseWallet(
+ GenerateAutonomousDatabaseWalletRequest.builder()
+ .autonomousDatabaseId(ocid)
+ .generateAutonomousDatabaseWalletDetails(walletDetails)
+ .build());
+
+ if (walletResponse.getContentLength() == 0) {
+ LOGGER.log(Level.SEVERE, "GenerateAutonomousDatabaseWalletResponse is empty");
+ res.status(Status.NOT_FOUND_404).send();
+ return;
+ }
+
+ byte[] walletContent;
+ try {
+ walletContent = walletResponse.getInputStream().readAllBytes();
+ } catch (IOException e) {
+ LOGGER.log(Level.SEVERE, "Error processing GenerateAutonomousDatabaseWalletResponse", e);
+ res.status(Status.INTERNAL_SERVER_ERROR_500).send();
+ return;
+ }
+
+ DbClient dbClient = createDbClient(walletContent);
+ Optional row = dbClient.execute().query("SELECT 'Hello world!!' FROM DUAL").findFirst();
+ if (row.isPresent()) {
+ res.send(row.get().column(1).as(String.class));
+ } else {
+ res.status(404).send();
+ }
+ }
+
+ DbClient createDbClient(byte[] walletContent) {
+ PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
+ try {
+ pds.setSSLContext(getSSLContext(walletContent));
+ pds.setURL(getJdbcUrl(walletContent, config.get("db.tnsNetServiceName")
+ .as(String.class)
+ .orElseThrow(() -> new IllegalStateException("Missing tnsNetServiceName!!"))));
+ pds.setUser(config.get("db.userName").as(String.class).orElse("ADMIN"));
+ pds.setPassword(config.get("db.password")
+ .as(String.class)
+ .orElseThrow(() -> new IllegalStateException("Missing password!!")));
+ pds.setConnectionFactoryClassName(OracleDataSource.class.getName());
+ } catch (SQLException e) {
+ LOGGER.log(Level.SEVERE, "Error setting up PoolDataSource", e);
+ throw new RuntimeException(e);
+ }
+ return new JdbcClientProvider().builder()
+ .connectionPool(() -> {
+ try {
+ return pds.getConnection();
+ } catch (SQLException e) {
+ throw new IllegalStateException("Error while setting up new connection", e);
+ }
+ })
+ .build();
+ }
+
+ /**
+ * Returns SSLContext based on cwallet.sso in wallet.
+ *
+ * @return SSLContext
+ */
+ private static SSLContext getSSLContext(byte[] walletContent) throws IllegalStateException {
+ SSLContext sslContext = null;
+ try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new ByteArrayInputStream(walletContent)))) {
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ if (entry.getName().equals("cwallet.sso")) {
+ KeyStore keyStore = KeyStore.getInstance("SSO", new OraclePKIProvider());
+ keyStore.load(zis, null);
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX");
+ trustManagerFactory.init(keyStore);
+ keyManagerFactory.init(keyStore, null);
+ sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
+ }
+ zis.closeEntry();
+ }
+ } catch (RuntimeException | Error throwMe) {
+ throw throwMe;
+ } catch (Exception e) {
+ throw new IllegalStateException("Error while getting SSLContext from wallet.", e);
+ }
+ return sslContext;
+ }
+
+ /**
+ * Returns JDBC URL with connection description for the given service based on {@code tnsnames.ora} in wallet.
+ *
+ * @param walletContent walletContent
+ * @param tnsNetServiceName tnsNetServiceName
+ * @return String
+ */
+ private static String getJdbcUrl(byte[] walletContent, String tnsNetServiceName) throws IllegalStateException {
+ String jdbcUrl = null;
+ try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new ByteArrayInputStream(walletContent)))) {
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ if (entry.getName().equals("tnsnames.ora")) {
+ jdbcUrl = new String(zis.readAllBytes(), StandardCharsets.UTF_8)
+ .replaceFirst(tnsNetServiceName + "\\s*=\\s*", "jdbc:oracle:thin:@")
+ .replaceAll("\\n[^\\n]+", "");
+ }
+ zis.closeEntry();
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Error while getting JDBC URL from wallet.", e);
+ }
+ return jdbcUrl;
+ }
+}
diff --git a/examples/integrations/oci/atp/src/main/java/io/helidon/examples/integrations/oci/atp/OciAtpMain.java b/examples/integrations/oci/atp/src/main/java/io/helidon/examples/integrations/oci/atp/OciAtpMain.java
new file mode 100644
index 000000000..0835e651d
--- /dev/null
+++ b/examples/integrations/oci/atp/src/main/java/io/helidon/examples/integrations/oci/atp/OciAtpMain.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.oci.atp;
+
+import java.io.IOException;
+
+import io.helidon.config.Config;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+
+import com.oracle.bmc.ConfigFileReader;
+import com.oracle.bmc.auth.AuthenticationDetailsProvider;
+import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider;
+import com.oracle.bmc.database.Database;
+import com.oracle.bmc.database.DatabaseClient;
+import com.oracle.bmc.model.BmcException;
+
+/**
+ * Main class of the example.
+ * This example sets up a web server to serve REST API to retrieve ATP wallet.
+ */
+public final class OciAtpMain {
+ /**
+ * Cannot be instantiated.
+ */
+ private OciAtpMain() {
+ }
+
+ /**
+ * Application main entry point.
+ *
+ * @param args command line arguments.
+ */
+ public static void main(String[] args) throws IOException {
+ // load logging configuration
+ LogConfig.configureRuntime();
+
+ // By default, this will pick up application.yaml from the classpath
+ Config config = Config.create();
+
+ // this requires OCI configuration in the usual place
+ // ~/.oci/config
+ ConfigFileReader.ConfigFile configFile = ConfigFileReader.parseDefault();
+ AuthenticationDetailsProvider authProvider = new ConfigFileAuthenticationDetailsProvider(configFile);
+ Database databaseClient = DatabaseClient.builder().build(authProvider);
+
+ // Prepare routing for the server
+ WebServer server = WebServer.builder()
+ .config(config.get("server"))
+ .routing(routing -> routing
+ .register("/atp", new AtpService(databaseClient, config))
+ // OCI SDK error handling
+ .error(BmcException.class, (req, res, ex) ->
+ res.status(ex.getStatusCode())
+ .send(ex.getMessage())))
+ .build()
+ .start();
+
+ System.out.println("WEB server is up! http://localhost:" + server.port() + "/");
+ }
+}
diff --git a/examples/integrations/oci/atp/src/main/java/io/helidon/examples/integrations/oci/atp/package-info.java b/examples/integrations/oci/atp/src/main/java/io/helidon/examples/integrations/oci/atp/package-info.java
new file mode 100644
index 000000000..aa9dfb8a1
--- /dev/null
+++ b/examples/integrations/oci/atp/src/main/java/io/helidon/examples/integrations/oci/atp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example of integration with OCI ATP in a Helidon SE application.
+ */
+package io.helidon.examples.integrations.oci.atp;
diff --git a/examples/integrations/oci/atp/src/main/resources/application.yaml b/examples/integrations/oci/atp/src/main/resources/application.yaml
new file mode 100644
index 000000000..a5261a306
--- /dev/null
+++ b/examples/integrations/oci/atp/src/main/resources/application.yaml
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# The values are read from
+# ~/helidon/conf/examples.yaml
+# or you can just update them here
+
+server:
+ port: 8080
+
+db:
+ userName: "${atp.db.userName}"
+ password: "${atp.db.password}"
+ tnsNetServiceName: "${atp.db.tnsNetServiceName}"
+
+oci:
+ atp:
+ ocid: "${oci.properties.atp-ocid}"
+ walletPassword: "${oci.properties.atp-walletPassword}"
diff --git a/examples/integrations/oci/atp/src/main/resources/logging.properties b/examples/integrations/oci/atp/src/main/resources/logging.properties
new file mode 100644
index 000000000..40dc82ff1
--- /dev/null
+++ b/examples/integrations/oci/atp/src/main/resources/logging.properties
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+io.helidon.level=INFO
+io.helidon.integrations.level=INFO
+io.helidon.webclient.level=INFO
diff --git a/examples/integrations/oci/metrics/README.md b/examples/integrations/oci/metrics/README.md
new file mode 100644
index 000000000..53a113400
--- /dev/null
+++ b/examples/integrations/oci/metrics/README.md
@@ -0,0 +1,9 @@
+The metrics example.
+
+The example requires OCI config in some default place like ``.oci/config``
+
+Build and run the example by
+```shell
+mvn package
+java -jar ./target/helidon-examples-integrations-oci-metrics.jar
+```
diff --git a/examples/integrations/oci/metrics/pom.xml b/examples/integrations/oci/metrics/pom.xml
new file mode 100644
index 000000000..1394845b3
--- /dev/null
+++ b/examples/integrations/oci/metrics/pom.xml
@@ -0,0 +1,73 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+
+
+ io.helidon.examples.integrations.oci.telemetry.OciMetricsMain
+
+
+ io.helidon.examples.integrations.oci
+ helidon-examples-integrations-oci-metrics
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration OCI Metrics
+ Integration with OCI Metrics.
+
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.logging
+ helidon-logging-jul
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-monitoring
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-common-httpclient-jersey3
+ runtime
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/integrations/oci/metrics/src/main/java/io/helidon/examples/integrations/oci/telemetry/OciMetricsMain.java b/examples/integrations/oci/metrics/src/main/java/io/helidon/examples/integrations/oci/telemetry/OciMetricsMain.java
new file mode 100644
index 000000000..dc25fcab0
--- /dev/null
+++ b/examples/integrations/oci/metrics/src/main/java/io/helidon/examples/integrations/oci/telemetry/OciMetricsMain.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.oci.telemetry;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import io.helidon.config.Config;
+import io.helidon.logging.common.LogConfig;
+
+import com.oracle.bmc.ConfigFileReader;
+import com.oracle.bmc.auth.AuthenticationDetailsProvider;
+import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider;
+import com.oracle.bmc.monitoring.Monitoring;
+import com.oracle.bmc.monitoring.MonitoringClient;
+import com.oracle.bmc.monitoring.model.Datapoint;
+import com.oracle.bmc.monitoring.model.FailedMetricRecord;
+import com.oracle.bmc.monitoring.model.MetricDataDetails;
+import com.oracle.bmc.monitoring.model.PostMetricDataDetails;
+import com.oracle.bmc.monitoring.model.PostMetricDataResponseDetails;
+import com.oracle.bmc.monitoring.requests.PostMetricDataRequest;
+import com.oracle.bmc.monitoring.responses.PostMetricDataResponse;
+
+import static io.helidon.config.ConfigSources.classpath;
+import static io.helidon.config.ConfigSources.file;
+
+/**
+ * OCI Metrics example.
+ */
+public final class OciMetricsMain {
+
+ private OciMetricsMain() {
+ }
+
+ /**
+ * Main method.
+ *
+ * @param args ignored
+ */
+ public static void main(String[] args) throws Exception {
+ LogConfig.configureRuntime();
+ // as I cannot share my configuration of OCI, let's combine the configuration
+ // from my home directory with the one compiled into the jar
+ // when running this example, you can either update the application.yaml in resources directory
+ // or use the same approach
+ Config config = buildConfig();
+
+ // this requires OCI configuration in the usual place
+ // ~/.oci/config
+ AuthenticationDetailsProvider authProvider = new ConfigFileAuthenticationDetailsProvider(ConfigFileReader.parseDefault());
+ try (Monitoring monitoringClient = MonitoringClient.builder().build(authProvider)) {
+ monitoringClient.setEndpoint(monitoringClient.getEndpoint().replace("telemetry.", "telemetry-ingestion."));
+
+ PostMetricDataRequest postMetricDataRequest = PostMetricDataRequest.builder()
+ .postMetricDataDetails(getPostMetricDataDetails(config))
+ .build();
+
+ // Invoke the API call.
+ PostMetricDataResponse postMetricDataResponse = monitoringClient.postMetricData(postMetricDataRequest);
+ PostMetricDataResponseDetails postMetricDataResponseDetails = postMetricDataResponse
+ .getPostMetricDataResponseDetails();
+ int count = postMetricDataResponseDetails.getFailedMetricsCount();
+ System.out.println("Failed count: " + count);
+ if (count > 0) {
+ System.out.println("Failed metrics:");
+ for (FailedMetricRecord failedMetric : postMetricDataResponseDetails.getFailedMetrics()) {
+ System.out.println("\t" + failedMetric.getMessage() + ": " + failedMetric.getMetricData());
+ }
+ }
+ }
+ }
+
+ private static PostMetricDataDetails getPostMetricDataDetails(Config config) {
+ String compartmentId = config.get("oci.metrics.compartment-ocid").asString().get();
+ Instant now = Instant.now();
+ return PostMetricDataDetails.builder()
+ .metricData(List.of(
+ MetricDataDetails.builder()
+ .compartmentId(compartmentId)
+ // Add a few data points to see something in the console
+ .datapoints(List.of(
+ Datapoint.builder()
+ .timestamp(Date.from(now.minus(10, ChronoUnit.SECONDS)))
+ .value(101.00)
+ .build(),
+ Datapoint.builder()
+ .timestamp(Date.from(now))
+ .value(149.00)
+ .build()
+ ))
+ .dimensions(Map.of(
+ "resourceId", "myresourceid",
+ "unit", "cm"))
+ .name("my_app.jump")
+ .namespace("helidon_examples")
+ .build()
+ ))
+ .batchAtomicity(PostMetricDataDetails.BatchAtomicity.NonAtomic).build();
+ }
+
+ private static Config buildConfig() {
+ return Config.builder()
+ .sources(
+ // you can use this file to override the defaults that are built-in
+ file(System.getProperty("user.home") + "/helidon/conf/examples.yaml").optional(),
+ // in jar file (see src/main/resources/application.yaml)
+ classpath("application.yaml"))
+ .build();
+ }
+}
diff --git a/examples/integrations/oci/metrics/src/main/java/io/helidon/examples/integrations/oci/telemetry/package-info.java b/examples/integrations/oci/metrics/src/main/java/io/helidon/examples/integrations/oci/telemetry/package-info.java
new file mode 100644
index 000000000..939bfb6ea
--- /dev/null
+++ b/examples/integrations/oci/metrics/src/main/java/io/helidon/examples/integrations/oci/telemetry/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example using OCI metrics blocking API.
+ */
+package io.helidon.examples.integrations.oci.telemetry;
diff --git a/examples/integrations/oci/metrics/src/main/resources/application.yaml b/examples/integrations/oci/metrics/src/main/resources/application.yaml
new file mode 100644
index 000000000..d8d676047
--- /dev/null
+++ b/examples/integrations/oci/metrics/src/main/resources/application.yaml
@@ -0,0 +1,23 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# The values are read from
+# ~/helidon/conf/examples.yaml
+# or you can just update them here
+oci:
+ metrics:
+ compartment-ocid: "${oci.properties.compartment-ocid}"
+
diff --git a/examples/integrations/oci/metrics/src/main/resources/logging.properties b/examples/integrations/oci/metrics/src/main/resources/logging.properties
new file mode 100644
index 000000000..8e28f63f9
--- /dev/null
+++ b/examples/integrations/oci/metrics/src/main/resources/logging.properties
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+io.helidon.level=INFO
+io.helidon.integrations.level=INFO
diff --git a/examples/integrations/oci/objectstorage-cdi/README.md b/examples/integrations/oci/objectstorage-cdi/README.md
new file mode 100644
index 000000000..aa08fbe0c
--- /dev/null
+++ b/examples/integrations/oci/objectstorage-cdi/README.md
@@ -0,0 +1,11 @@
+The object storage (CDI) example.
+
+The example requires OCI config in some default place like ``.oci/config``
+Also properties from the ``src/main/resources/application.yaml`` shall be configured.
+Like ``oci.objectstorage.bucketName``
+
+Build and run the example by
+```shell
+mvn package
+java -jar ./target/helidon-examples-integrations-oci-objectstorage-cdi.jar
+```
diff --git a/examples/integrations/oci/objectstorage-cdi/pom.xml b/examples/integrations/oci/objectstorage-cdi/pom.xml
new file mode 100644
index 000000000..6dbfa582c
--- /dev/null
+++ b/examples/integrations/oci/objectstorage-cdi/pom.xml
@@ -0,0 +1,87 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+
+ io.helidon.examples.integrations.oci
+ helidon-examples-integrations-oci-objectstorage-cdi
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration OCI Object Storage CDI
+ CDI integration with OCI Object Storage.
+
+
+ io.helidon.examples.integrations.oci.objectstorage.cdi.ObjectStorageCdiMain
+
+
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile
+
+
+ io.helidon.integrations.oci.sdk
+ helidon-integrations-oci-sdk-cdi
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-objectstorage
+
+
+ io.helidon.config
+ helidon-config-yaml-mp
+
+
+ io.smallrye
+ jandex
+ runtime
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
+
diff --git a/examples/integrations/oci/objectstorage-cdi/src/main/java/io/helidon/examples/integrations/oci/objectstorage/cdi/ObjectStorageCdiMain.java b/examples/integrations/oci/objectstorage-cdi/src/main/java/io/helidon/examples/integrations/oci/objectstorage/cdi/ObjectStorageCdiMain.java
new file mode 100644
index 000000000..f80182564
--- /dev/null
+++ b/examples/integrations/oci/objectstorage-cdi/src/main/java/io/helidon/examples/integrations/oci/objectstorage/cdi/ObjectStorageCdiMain.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.oci.objectstorage.cdi;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import io.helidon.config.yaml.mp.YamlMpConfigSource;
+import io.helidon.microprofile.cdi.Main;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
+/**
+ * Main class of the example.
+ * This is only used to merge configuration from home directory with the one embedded on classpath.
+ */
+public final class ObjectStorageCdiMain {
+ private ObjectStorageCdiMain() {
+ }
+
+ /**
+ * Main method.
+ *
+ * @param args ignored
+ */
+ public static void main(String[] args) {
+ ConfigProviderResolver configProvider = ConfigProviderResolver.instance();
+
+ Config mpConfig = configProvider.getBuilder()
+ .addDefaultSources()
+ .withSources(examplesConfig())
+ .addDiscoveredSources()
+ .addDiscoveredConverters()
+ .build();
+
+ // configure
+ configProvider.registerConfig(mpConfig, null);
+
+ // start CDI
+ Main.main(args);
+ }
+
+ private static ConfigSource[] examplesConfig() {
+ Path path = Paths.get(System.getProperty("user.home") + "/helidon/conf/examples.yaml");
+ if (Files.exists(path)) {
+ return new ConfigSource[] {YamlMpConfigSource.create(path)};
+ }
+ return new ConfigSource[0];
+ }
+}
diff --git a/examples/integrations/oci/objectstorage-cdi/src/main/java/io/helidon/examples/integrations/oci/objectstorage/cdi/ObjectStorageResource.java b/examples/integrations/oci/objectstorage-cdi/src/main/java/io/helidon/examples/integrations/oci/objectstorage/cdi/ObjectStorageResource.java
new file mode 100644
index 000000000..3db4c5a9e
--- /dev/null
+++ b/examples/integrations/oci/objectstorage-cdi/src/main/java/io/helidon/examples/integrations/oci/objectstorage/cdi/ObjectStorageResource.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.oci.objectstorage.cdi;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import io.helidon.http.HeaderNames;
+
+import com.oracle.bmc.objectstorage.ObjectStorage;
+import com.oracle.bmc.objectstorage.requests.DeleteObjectRequest;
+import com.oracle.bmc.objectstorage.requests.GetNamespaceRequest;
+import com.oracle.bmc.objectstorage.requests.GetObjectRequest;
+import com.oracle.bmc.objectstorage.requests.PutObjectRequest;
+import com.oracle.bmc.objectstorage.responses.DeleteObjectResponse;
+import com.oracle.bmc.objectstorage.responses.GetNamespaceResponse;
+import com.oracle.bmc.objectstorage.responses.GetObjectResponse;
+import com.oracle.bmc.objectstorage.responses.PutObjectResponse;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.Response;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+/**
+ * JAX-RS resource - REST API for the objecstorage example.
+ */
+@Path("/files")
+public class ObjectStorageResource {
+ private static final Logger LOGGER = Logger.getLogger(ObjectStorageResource.class.getName());
+ private final ObjectStorage objectStorageClient;
+ private final String namespaceName;
+ private final String bucketName;
+
+ @Inject
+ ObjectStorageResource(ObjectStorage objectStorageClient,
+ @ConfigProperty(name = "oci.objectstorage.bucketName")
+ String bucketName) {
+ this.objectStorageClient = objectStorageClient;
+ this.bucketName = bucketName;
+ GetNamespaceResponse namespaceResponse =
+ this.objectStorageClient.getNamespace(GetNamespaceRequest.builder().build());
+ this.namespaceName = namespaceResponse.getValue();
+ }
+
+ /**
+ * Download a file from object storage.
+ *
+ * @param fileName name of the object
+ * @return response
+ */
+ @GET
+ @Path("/file/{file-name}")
+ public Response download(@PathParam("file-name") String fileName) {
+ GetObjectResponse getObjectResponse =
+ objectStorageClient.getObject(
+ GetObjectRequest.builder()
+ .namespaceName(namespaceName)
+ .bucketName(bucketName)
+ .objectName(fileName)
+ .build());
+
+ if (getObjectResponse.getContentLength() == 0) {
+ LOGGER.log(Level.SEVERE, "GetObjectResponse is empty");
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+
+ try (InputStream fileStream = getObjectResponse.getInputStream()) {
+ byte[] objectContent = fileStream.readAllBytes();
+ Response.ResponseBuilder ok = Response.ok(objectContent)
+ .header(HeaderNames.CONTENT_DISPOSITION.defaultCase(), "attachment; filename=\"" + fileName + "\"")
+ .header("opc-request-id", getObjectResponse.getOpcRequestId())
+ .header("request-id", getObjectResponse.getOpcClientRequestId())
+ .header(HeaderNames.CONTENT_LENGTH.defaultCase(), getObjectResponse.getContentLength());
+
+ return ok.build();
+ } catch (IOException e) {
+ LOGGER.log(Level.SEVERE, "Error processing GetObjectResponse", e);
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ }
+ }
+
+ /**
+ * Upload a file to object storage.
+ *
+ * @param fileName name of the object
+ * @return response
+ */
+ @POST
+ @Path("/file/{fileName}")
+ public Response upload(@PathParam("fileName") String fileName) {
+
+ PutObjectRequest putObjectRequest = null;
+ try (InputStream stream = new FileInputStream(System.getProperty("user.dir") + File.separator + fileName)) {
+ byte[] contents = stream.readAllBytes();
+ putObjectRequest =
+ PutObjectRequest.builder()
+ .namespaceName(namespaceName)
+ .bucketName(bucketName)
+ .objectName(fileName)
+ .putObjectBody(new ByteArrayInputStream(contents))
+ .contentLength(Long.valueOf(contents.length))
+ .build();
+ } catch (IOException e) {
+ LOGGER.log(Level.SEVERE, "Error creating PutObjectRequest", e);
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ }
+ PutObjectResponse putObjectResponse = objectStorageClient.putObject(putObjectRequest);
+
+ Response.ResponseBuilder ok = Response.ok()
+ .header("opc-request-id", putObjectResponse.getOpcRequestId())
+ .header("request-id", putObjectResponse.getOpcClientRequestId());
+
+ return ok.build();
+ }
+
+ /**
+ * Delete a file from object storage.
+ *
+ * @param fileName object name
+ * @return response
+ */
+ @DELETE
+ @Path("/file/{file-name}")
+ public Response delete(@PathParam("file-name") String fileName) {
+ DeleteObjectResponse deleteObjectResponse = objectStorageClient.deleteObject(DeleteObjectRequest.builder()
+ .namespaceName(namespaceName)
+ .bucketName(bucketName)
+ .objectName(fileName)
+ .build());
+ Response.ResponseBuilder ok = Response.ok()
+ .header("opc-request-id", deleteObjectResponse.getOpcRequestId())
+ .header("request-id", deleteObjectResponse.getOpcClientRequestId());
+
+ return ok.build();
+ }
+}
diff --git a/examples/integrations/oci/objectstorage-cdi/src/main/java/io/helidon/examples/integrations/oci/objectstorage/cdi/package-info.java b/examples/integrations/oci/objectstorage-cdi/src/main/java/io/helidon/examples/integrations/oci/objectstorage/cdi/package-info.java
new file mode 100644
index 000000000..20453fd0c
--- /dev/null
+++ b/examples/integrations/oci/objectstorage-cdi/src/main/java/io/helidon/examples/integrations/oci/objectstorage/cdi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example of integration with OCI object storage in a CDI application.
+ */
+package io.helidon.examples.integrations.oci.objectstorage.cdi;
diff --git a/examples/integrations/oci/objectstorage-cdi/src/main/resources/META-INF/beans.xml b/examples/integrations/oci/objectstorage-cdi/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..e149ced7d
--- /dev/null
+++ b/examples/integrations/oci/objectstorage-cdi/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/integrations/oci/objectstorage-cdi/src/main/resources/application.yaml b/examples/integrations/oci/objectstorage-cdi/src/main/resources/application.yaml
new file mode 100644
index 000000000..097e80035
--- /dev/null
+++ b/examples/integrations/oci/objectstorage-cdi/src/main/resources/application.yaml
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8080
+
+#
+# The following properties under oci are accessed by Helidon. Values
+# under oci.properties.* are read from ~/helidon/conf/examples.yaml.
+#
+oci:
+ objectstorage:
+ bucketName: "${oci.properties.objectstorage-bucketName}"
diff --git a/examples/integrations/oci/objectstorage-cdi/src/main/resources/logging.properties b/examples/integrations/oci/objectstorage-cdi/src/main/resources/logging.properties
new file mode 100644
index 000000000..40dc82ff1
--- /dev/null
+++ b/examples/integrations/oci/objectstorage-cdi/src/main/resources/logging.properties
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+io.helidon.level=INFO
+io.helidon.integrations.level=INFO
+io.helidon.webclient.level=INFO
diff --git a/examples/integrations/oci/objectstorage/README.md b/examples/integrations/oci/objectstorage/README.md
new file mode 100644
index 000000000..06c5c52ba
--- /dev/null
+++ b/examples/integrations/oci/objectstorage/README.md
@@ -0,0 +1,9 @@
+The object storage example.
+
+The example requires OCI config in some default place like ``.oci/config``
+
+Build and run the example by
+```shell
+mvn package
+java -jar ./target/helidon-examples-integrations-oci-objectstorage.jar
+```
diff --git a/examples/integrations/oci/objectstorage/pom.xml b/examples/integrations/oci/objectstorage/pom.xml
new file mode 100644
index 000000000..28af1a635
--- /dev/null
+++ b/examples/integrations/oci/objectstorage/pom.xml
@@ -0,0 +1,73 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+
+ io.helidon.examples.integrations.oci
+ helidon-examples-integrations-oci-objectstorage
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration OCI Object Storage
+ Integration with OCI Object Storage.
+
+
+ io.helidon.examples.integrations.oci.objecstorage.OciObjectStorageMain
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-objectstorage
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-common-httpclient-jersey3
+ runtime
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/integrations/oci/objectstorage/src/main/java/io/helidon/examples/integrations/oci/objecstorage/ObjectStorageService.java b/examples/integrations/oci/objectstorage/src/main/java/io/helidon/examples/integrations/oci/objecstorage/ObjectStorageService.java
new file mode 100644
index 000000000..d336f43f3
--- /dev/null
+++ b/examples/integrations/oci/objectstorage/src/main/java/io/helidon/examples/integrations/oci/objecstorage/ObjectStorageService.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.oci.objecstorage;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigException;
+import io.helidon.http.HeaderNames;
+import io.helidon.http.Status;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+import com.oracle.bmc.ConfigFileReader;
+import com.oracle.bmc.auth.AuthenticationDetailsProvider;
+import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider;
+import com.oracle.bmc.objectstorage.ObjectStorage;
+import com.oracle.bmc.objectstorage.ObjectStorageClient;
+import com.oracle.bmc.objectstorage.requests.DeleteObjectRequest;
+import com.oracle.bmc.objectstorage.requests.GetNamespaceRequest;
+import com.oracle.bmc.objectstorage.requests.GetObjectRequest;
+import com.oracle.bmc.objectstorage.requests.PutObjectRequest;
+import com.oracle.bmc.objectstorage.responses.DeleteObjectResponse;
+import com.oracle.bmc.objectstorage.responses.GetNamespaceResponse;
+import com.oracle.bmc.objectstorage.responses.GetObjectResponse;
+import com.oracle.bmc.objectstorage.responses.PutObjectResponse;
+
+/**
+ * REST API for the objecstorage example.
+ */
+public class ObjectStorageService implements HttpService {
+ private static final Logger LOGGER = Logger.getLogger(ObjectStorageService.class.getName());
+ private final ObjectStorage objectStorageClient;
+ private final String namespaceName;
+ private final String bucketName;
+
+
+ ObjectStorageService(Config config) {
+ try {
+ ConfigFileReader.ConfigFile configFile = ConfigFileReader.parseDefault();
+ AuthenticationDetailsProvider authProvider = new ConfigFileAuthenticationDetailsProvider(configFile);
+ this.objectStorageClient = ObjectStorageClient.builder().build(authProvider);
+ this.bucketName = config.get("oci.objectstorage.bucketName")
+ .asString()
+ .orElseThrow(() -> new IllegalStateException("Missing bucket name!!"));
+ GetNamespaceResponse namespaceResponse =
+ this.objectStorageClient.getNamespace(GetNamespaceRequest.builder().build());
+ this.namespaceName = namespaceResponse.getValue();
+ } catch (IOException e) {
+ throw new ConfigException("Failed to read configuration properties", e);
+ }
+ }
+
+ /**
+ * A service registers itself by updating the routine rules.
+ *
+ * @param rules the routing rules.
+ */
+ public void routing(HttpRules rules) {
+ rules.get("/file/{file-name}", this::download);
+ rules.post("/file/{fileName}", this::upload);
+ rules.delete("/file/{file-name}", this::delete);
+ }
+
+ /**
+ * Download a file from object storage.
+ *
+ * @param request request
+ * @param response response
+ */
+ public void download(ServerRequest request, ServerResponse response) {
+ String fileName = request.path().pathParameters().get("file-name");
+ GetObjectResponse getObjectResponse =
+ objectStorageClient.getObject(
+ GetObjectRequest.builder()
+ .namespaceName(namespaceName)
+ .bucketName(bucketName)
+ .objectName(fileName)
+ .build());
+
+ if (getObjectResponse.getContentLength() == 0) {
+ LOGGER.log(Level.SEVERE, "GetObjectResponse is empty");
+ response.status(Status.NOT_FOUND_404).send();
+ return;
+ }
+
+ try (InputStream fileStream = getObjectResponse.getInputStream()) {
+ byte[] objectContent = fileStream.readAllBytes();
+ response
+ .status(Status.OK_200)
+ .header(HeaderNames.CONTENT_DISPOSITION.defaultCase(), "attachment; filename=\"" + fileName + "\"")
+ .header("opc-request-id", getObjectResponse.getOpcRequestId())
+ .header(HeaderNames.CONTENT_LENGTH.defaultCase(), getObjectResponse.getContentLength().toString());
+
+ response.send(objectContent);
+ } catch (IOException e) {
+ LOGGER.log(Level.SEVERE, "Error processing GetObjectResponse", e);
+ response.status(Status.INTERNAL_SERVER_ERROR_500).send();
+ }
+ }
+
+ /**
+ * Upload a file to object storage.
+ *
+ * @param request request
+ * @param response response
+ */
+ public void upload(ServerRequest request, ServerResponse response) {
+ String fileName = request.path().pathParameters().get("fileName");
+ PutObjectRequest putObjectRequest;
+ try (InputStream stream = new FileInputStream(System.getProperty("user.dir") + File.separator + fileName)) {
+ byte[] contents = stream.readAllBytes();
+ putObjectRequest =
+ PutObjectRequest.builder()
+ .namespaceName(namespaceName)
+ .bucketName(bucketName)
+ .objectName(fileName)
+ .putObjectBody(new ByteArrayInputStream(contents))
+ .contentLength((long) contents.length)
+ .build();
+ } catch (IOException e) {
+ LOGGER.log(Level.SEVERE, "Error creating PutObjectRequest", e);
+ response.status(Status.INTERNAL_SERVER_ERROR_500).send();
+ return;
+ }
+ PutObjectResponse putObjectResponse = objectStorageClient.putObject(putObjectRequest);
+
+ response.status(Status.OK_200).header("opc-request-id", putObjectResponse.getOpcRequestId());
+
+ response.send();
+ }
+
+ /**
+ * Delete a file from object storage.
+ *
+ * @param request request
+ * @param response response
+ */
+ public void delete(ServerRequest request, ServerResponse response) {
+ String fileName = request.path().pathParameters().get("file-name");
+ DeleteObjectResponse deleteObjectResponse = objectStorageClient.deleteObject(DeleteObjectRequest.builder()
+ .namespaceName(namespaceName)
+ .bucketName(bucketName)
+ .objectName(fileName)
+ .build());
+ response.status(Status.OK_200).header("opc-request-id", deleteObjectResponse.getOpcRequestId());
+
+ response.send();
+ }
+}
diff --git a/examples/integrations/oci/objectstorage/src/main/java/io/helidon/examples/integrations/oci/objecstorage/OciObjectStorageMain.java b/examples/integrations/oci/objectstorage/src/main/java/io/helidon/examples/integrations/oci/objecstorage/OciObjectStorageMain.java
new file mode 100644
index 000000000..9453bab0f
--- /dev/null
+++ b/examples/integrations/oci/objectstorage/src/main/java/io/helidon/examples/integrations/oci/objecstorage/OciObjectStorageMain.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.oci.objecstorage;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import io.helidon.config.Config;
+import io.helidon.config.spi.ConfigSource;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.http.HttpRouting;
+
+import static io.helidon.config.ConfigSources.classpath;
+import static io.helidon.config.ConfigSources.file;
+
+/**
+ * Main class of the example.
+ * This example sets up a web server to serve REST API to upload/download/delete objects.
+ */
+public final class OciObjectStorageMain {
+
+ private OciObjectStorageMain() {
+ }
+
+ /**
+ * Main method.
+ *
+ * @param args ignored
+ */
+ public static void main(String[] args) {
+ Config config = Config
+ .builder()
+ .sources(examplesConfig())
+ .build();
+ WebServer.builder()
+ .routing(r -> routing(r, config))
+ .config(config.get("server"))
+ .build()
+ .start();
+ }
+
+ /**
+ * Updates HTTP Routing.
+ *
+ * @param routing routing builder
+ * @param config config
+ */
+ static void routing(HttpRouting.Builder routing, Config config) {
+ ObjectStorageService objectStorageService = new ObjectStorageService(config);
+ routing.register("/files", objectStorageService);
+ }
+
+ private static List> examplesConfig() {
+ List> suppliers = new ArrayList<>();
+ Path path = Paths.get(System.getProperty("user.home") + "/helidon/conf/examples.yaml");
+ if (Files.exists(path)) {
+ suppliers.add(file(path).build());
+ }
+ suppliers.add(classpath("application.yaml").build());
+ return suppliers;
+ }
+}
diff --git a/examples/integrations/oci/objectstorage/src/main/java/io/helidon/examples/integrations/oci/objecstorage/package-info.java b/examples/integrations/oci/objectstorage/src/main/java/io/helidon/examples/integrations/oci/objecstorage/package-info.java
new file mode 100644
index 000000000..ffa5134cb
--- /dev/null
+++ b/examples/integrations/oci/objectstorage/src/main/java/io/helidon/examples/integrations/oci/objecstorage/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example of integration with OCI object storage in a Helidon SE application.
+ */
+package io.helidon.examples.integrations.oci.objecstorage;
diff --git a/examples/integrations/oci/objectstorage/src/main/resources/application.yaml b/examples/integrations/oci/objectstorage/src/main/resources/application.yaml
new file mode 100644
index 000000000..2e4b34c10
--- /dev/null
+++ b/examples/integrations/oci/objectstorage/src/main/resources/application.yaml
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# The values are read from
+# ~/helidon/conf/examples.yaml
+# or you can just update them here
+
+server:
+ port: 8080
+
+oci:
+ objectstorage:
+ bucketName: "${oci.properties.objectstorage-bucketName}"
diff --git a/examples/integrations/oci/objectstorage/src/main/resources/logging.properties b/examples/integrations/oci/objectstorage/src/main/resources/logging.properties
new file mode 100644
index 000000000..40dc82ff1
--- /dev/null
+++ b/examples/integrations/oci/objectstorage/src/main/resources/logging.properties
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+io.helidon.level=INFO
+io.helidon.integrations.level=INFO
+io.helidon.webclient.level=INFO
diff --git a/examples/integrations/oci/pom.xml b/examples/integrations/oci/pom.xml
new file mode 100644
index 000000000..4ecd5ad83
--- /dev/null
+++ b/examples/integrations/oci/pom.xml
@@ -0,0 +1,46 @@
+
+
+
+ 4.0.0
+
+ io.helidon.examples.integrations
+ helidon-examples-integrations-project
+ 1.0.0-SNAPSHOT
+
+ io.helidon.examples.integrations.oci
+ helidon-examples-integrations-oci-project
+ 1.0.0-SNAPSHOT
+ pom
+ Helidon Examples Integration OCI
+ Examples of integration with OCI (Oracle Cloud).
+
+
+ atp
+ atp-cdi
+ metrics
+ objectstorage
+ objectstorage-cdi
+ vault
+ vault-cdi
+
+
+
diff --git a/examples/integrations/oci/vault-cdi/README.md b/examples/integrations/oci/vault-cdi/README.md
new file mode 100644
index 000000000..aba4193e1
--- /dev/null
+++ b/examples/integrations/oci/vault-cdi/README.md
@@ -0,0 +1,12 @@
+The vault (CDI) example.
+
+The example requires OCI config in some default place like ``.oci/config``
+
+Also properties from the ``src/main/resources/application.yaml`` shall be configured.
+Like ``oci.properties.compartment-ocid``
+
+Build and run the example by
+```shell
+mvn package
+java -jar ./target/helidon-examples-integrations-oci-vault-cdi.jar
+```
\ No newline at end of file
diff --git a/examples/integrations/oci/vault-cdi/pom.xml b/examples/integrations/oci/vault-cdi/pom.xml
new file mode 100644
index 000000000..6b689cf5f
--- /dev/null
+++ b/examples/integrations/oci/vault-cdi/pom.xml
@@ -0,0 +1,101 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+
+ io.helidon.examples.integrations.oci
+ helidon-examples-integrations-oci-vault-cdi
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration OCI Object Storage CDI
+ CDI integration with OCI Object Storage.
+
+
+ io.helidon.examples.integrations.oci.vault.cdi.VaultCdiMain
+
+
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile
+
+
+ io.helidon.integrations.oci.sdk
+ helidon-integrations-oci-sdk-cdi
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-keymanagement
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-secrets
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-vault
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.microprofile.testing
+ helidon-microprofile-testing-junit5
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
+
diff --git a/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/CryptoClientProducer.java b/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/CryptoClientProducer.java
new file mode 100644
index 000000000..cb122b112
--- /dev/null
+++ b/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/CryptoClientProducer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.oci.vault.cdi;
+
+import com.oracle.bmc.keymanagement.KmsCryptoClient;
+import com.oracle.bmc.keymanagement.KmsCryptoClientBuilder;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Produces;
+import jakarta.inject.Inject;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+/**
+ * KMS crypto client (used for encryption, decryption and signatures) requires additional configuration, that cannot
+ * be done automatically by the SDK.
+ */
+@ApplicationScoped
+class CryptoClientProducer {
+ private final String cryptoEndpoint;
+
+ @Inject
+ CryptoClientProducer(@ConfigProperty(name = "app.vault.cryptographic-endpoint")
+ String cryptoEndpoint) {
+ this.cryptoEndpoint = cryptoEndpoint;
+ }
+
+ @Produces
+ KmsCryptoClientBuilder clientBuilder() {
+ return KmsCryptoClient.builder()
+ .endpoint(cryptoEndpoint);
+ }
+}
diff --git a/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/ErrorHandlerProvider.java b/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/ErrorHandlerProvider.java
new file mode 100644
index 000000000..a90d2cd88
--- /dev/null
+++ b/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/ErrorHandlerProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.oci.vault.cdi;
+
+import com.oracle.bmc.model.BmcException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+/**
+ * Maps SDK errors to HTTP errors, as otherwise any exception is manifested as an internal server error.
+ * This mapper is not part of integration with OCI SDK, as each application may require a different entity format.
+ * This mapper simply uses the response code as HTTP status code, and error message as entity.
+ */
+@Provider
+class ErrorHandlerProvider implements ExceptionMapper {
+ @Override
+ public Response toResponse(BmcException e) {
+ return Response.status(e.getStatusCode())
+ .entity(e.getMessage())
+ .build();
+ }
+}
diff --git a/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/VaultCdiMain.java b/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/VaultCdiMain.java
new file mode 100644
index 000000000..641934080
--- /dev/null
+++ b/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/VaultCdiMain.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.oci.vault.cdi;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import io.helidon.config.yaml.mp.YamlMpConfigSource;
+import io.helidon.microprofile.cdi.Main;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
+/**
+ * Main class of the example.
+ * Used only to set up configuration.
+ */
+public final class VaultCdiMain {
+ private VaultCdiMain() {
+ }
+
+ /**
+ * Main method.
+ *
+ * @param args ignored
+ */
+ public static void main(String[] args) {
+ ConfigProviderResolver configProvider = ConfigProviderResolver.instance();
+
+ Config mpConfig = configProvider.getBuilder()
+ .addDefaultSources()
+ .withSources(examplesConfig())
+ .addDiscoveredSources()
+ .addDiscoveredConverters()
+ .build();
+
+ // configure
+ configProvider.registerConfig(mpConfig, null);
+
+ // start CDI
+ Main.main(args);
+ }
+
+ private static ConfigSource[] examplesConfig() {
+ Path path = Paths.get(System.getProperty("user.home") + "/helidon/conf/examples.yaml");
+ if (Files.exists(path)) {
+ return new ConfigSource[] {YamlMpConfigSource.create(path)};
+ }
+ return new ConfigSource[0];
+ }
+}
diff --git a/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/VaultResource.java b/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/VaultResource.java
new file mode 100644
index 000000000..c2a1c4312
--- /dev/null
+++ b/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/VaultResource.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.oci.vault.cdi;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+
+import io.helidon.common.Base64Value;
+
+import com.oracle.bmc.keymanagement.KmsCrypto;
+import com.oracle.bmc.keymanagement.model.DecryptDataDetails;
+import com.oracle.bmc.keymanagement.model.EncryptDataDetails;
+import com.oracle.bmc.keymanagement.model.SignDataDetails;
+import com.oracle.bmc.keymanagement.model.VerifyDataDetails;
+import com.oracle.bmc.keymanagement.requests.DecryptRequest;
+import com.oracle.bmc.keymanagement.requests.EncryptRequest;
+import com.oracle.bmc.keymanagement.requests.SignRequest;
+import com.oracle.bmc.keymanagement.requests.VerifyRequest;
+import com.oracle.bmc.secrets.Secrets;
+import com.oracle.bmc.secrets.model.Base64SecretBundleContentDetails;
+import com.oracle.bmc.secrets.model.SecretBundleContentDetails;
+import com.oracle.bmc.secrets.requests.GetSecretBundleRequest;
+import com.oracle.bmc.vault.Vaults;
+import com.oracle.bmc.vault.model.Base64SecretContentDetails;
+import com.oracle.bmc.vault.model.CreateSecretDetails;
+import com.oracle.bmc.vault.model.ScheduleSecretDeletionDetails;
+import com.oracle.bmc.vault.model.SecretContentDetails;
+import com.oracle.bmc.vault.requests.CreateSecretRequest;
+import com.oracle.bmc.vault.requests.ScheduleSecretDeletionRequest;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.InternalServerErrorException;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+/**
+ * JAX-RS resource - REST API of the example.
+ */
+@Path("/vault")
+public class VaultResource {
+ private final Secrets secrets;
+ private final KmsCrypto crypto;
+ private final Vaults vaults;
+ private final String vaultOcid;
+ private final String compartmentOcid;
+ private final String encryptionKeyOcid;
+ private final String signatureKeyOcid;
+
+ @Inject
+ VaultResource(Secrets secrets,
+ KmsCrypto crypto,
+ Vaults vaults,
+ @ConfigProperty(name = "app.vault.vault-ocid")
+ String vaultOcid,
+ @ConfigProperty(name = "app.vault.compartment-ocid")
+ String compartmentOcid,
+ @ConfigProperty(name = "app.vault.encryption-key-ocid")
+ String encryptionKeyOcid,
+ @ConfigProperty(name = "app.vault.signature-key-ocid")
+ String signatureKeyOcid) {
+ this.secrets = secrets;
+ this.crypto = crypto;
+ this.vaults = vaults;
+ this.vaultOcid = vaultOcid;
+ this.compartmentOcid = compartmentOcid;
+ this.encryptionKeyOcid = encryptionKeyOcid;
+ this.signatureKeyOcid = signatureKeyOcid;
+ }
+
+ /**
+ * Encrypt a string.
+ *
+ * @param secret secret to encrypt
+ * @return cipher text
+ */
+ @GET
+ @Path("/encrypt/{text}")
+ public String encrypt(@PathParam("text") String secret) {
+ return crypto.encrypt(EncryptRequest.builder()
+ .encryptDataDetails(EncryptDataDetails.builder()
+ .keyId(encryptionKeyOcid)
+ .plaintext(Base64Value.create(secret).toBase64())
+ .build())
+ .build())
+ .getEncryptedData()
+ .getCiphertext();
+ }
+
+ /**
+ * Decrypt a cipher text.
+ *
+ * @param cipherText cipher text to decrypt
+ * @return original secret
+ */
+ @GET
+ @Path("/decrypt/{text: .*}")
+ public String decrypt(@PathParam("text") String cipherText) {
+ return Base64Value.createFromEncoded(crypto.decrypt(DecryptRequest.builder()
+ .decryptDataDetails(DecryptDataDetails.builder()
+ .keyId(encryptionKeyOcid)
+ .ciphertext(cipherText)
+ .build())
+ .build())
+ .getDecryptedData()
+ .getPlaintext())
+ .toDecodedString();
+ }
+
+ /**
+ * Sign data.
+ *
+ * @param dataToSign data to sign (must be a String)
+ * @return signature text
+ */
+ @GET
+ @Path("/sign/{text}")
+ public String sign(@PathParam("text") String dataToSign) {
+ return crypto.sign(SignRequest.builder()
+ .signDataDetails(SignDataDetails.builder()
+ .keyId(signatureKeyOcid)
+ .signingAlgorithm(SignDataDetails.SigningAlgorithm.Sha224RsaPkcsPss)
+ .message(Base64Value.create(dataToSign).toBase64())
+ .build())
+ .build())
+ .getSignedData()
+ .getSignature();
+ }
+
+ /**
+ * Verify a signature. The base64 encoded signature is the entity
+ *
+ * @param dataToVerify data that was signed
+ * @param signature signature text
+ * @return whether the signature is valid or not
+ */
+ @POST
+ @Path("/verify/{text}")
+ public String verify(@PathParam("text") String dataToVerify,
+ String signature) {
+ VerifyDataDetails.SigningAlgorithm algorithm = VerifyDataDetails.SigningAlgorithm.Sha224RsaPkcsPss;
+
+ boolean valid = crypto.verify(VerifyRequest.builder()
+ .verifyDataDetails(VerifyDataDetails.builder()
+ .keyId(signatureKeyOcid)
+ .signingAlgorithm(algorithm)
+ .message(Base64Value.create(dataToVerify).toBase64())
+ .signature(signature)
+ .build())
+ .build())
+ .getVerifiedData()
+ .getIsSignatureValid();
+
+ return valid ? "Signature valid" : "Signature not valid";
+ }
+
+ /**
+ * Get secret content from Vault.
+ *
+ * @param secretOcid OCID of the secret to get
+ * @return content of the secret
+ */
+ @GET
+ @Path("/secret/{id}")
+ public String getSecret(@PathParam("id") String secretOcid) {
+ SecretBundleContentDetails content = secrets.getSecretBundle(GetSecretBundleRequest.builder()
+ .secretId(secretOcid)
+ .build())
+ .getSecretBundle()
+ .getSecretBundleContent();
+
+ if (content instanceof Base64SecretBundleContentDetails) {
+ // the only supported type
+ return Base64Value.createFromEncoded(((Base64SecretBundleContentDetails) content).getContent()).toDecodedString();
+ } else {
+ throw new InternalServerErrorException("Invalid secret content type");
+ }
+ }
+
+ /**
+ * Delete a secret from Vault.
+ * This operation actually marks a secret for deletion, and the minimal time is 30 days.
+ *
+ * @param secretOcid OCID of the secret to delete
+ * @return short message
+ */
+ @DELETE
+ @Path("/secret/{id}")
+ public String deleteSecret(@PathParam("id") String secretOcid) {
+ // has to be for quite a long period of time - did not work with less than 30 days
+ Date deleteTime = Date.from(Instant.now().plus(30, ChronoUnit.DAYS));
+
+ vaults.scheduleSecretDeletion(ScheduleSecretDeletionRequest.builder()
+ .secretId(secretOcid)
+ .scheduleSecretDeletionDetails(ScheduleSecretDeletionDetails.builder()
+ .timeOfDeletion(deleteTime)
+ .build())
+ .build());
+
+ return "Secret " + secretOcid + " was marked for deletion";
+ }
+
+ /**
+ * Create a new secret.
+ *
+ * @param name name of the secret
+ * @param secretText secret content
+ * @return OCID of the created secret
+ */
+ @POST
+ @Path("/secret/{name}")
+ public String createSecret(@PathParam("name") String name,
+ String secretText) {
+ SecretContentDetails content = Base64SecretContentDetails.builder()
+ .content(Base64Value.create(secretText).toBase64())
+ .build();
+
+ return vaults.createSecret(CreateSecretRequest.builder()
+ .createSecretDetails(CreateSecretDetails.builder()
+ .secretName(name)
+ .vaultId(vaultOcid)
+ .compartmentId(compartmentOcid)
+ .keyId(encryptionKeyOcid)
+ .secretContent(content)
+ .build())
+ .build())
+ .getSecret()
+ .getId();
+ }
+}
diff --git a/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/package-info.java b/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/package-info.java
new file mode 100644
index 000000000..2cdf8f233
--- /dev/null
+++ b/examples/integrations/oci/vault-cdi/src/main/java/io/helidon/examples/integrations/oci/vault/cdi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example of OCI Vault integration in CDI.
+ */
+package io.helidon.examples.integrations.oci.vault.cdi;
diff --git a/examples/integrations/oci/vault-cdi/src/main/resources/META-INF/beans.xml b/examples/integrations/oci/vault-cdi/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..e149ced7d
--- /dev/null
+++ b/examples/integrations/oci/vault-cdi/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/integrations/oci/vault-cdi/src/main/resources/application.yaml b/examples/integrations/oci/vault-cdi/src/main/resources/application.yaml
new file mode 100644
index 000000000..c0fe46401
--- /dev/null
+++ b/examples/integrations/oci/vault-cdi/src/main/resources/application.yaml
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8080
+
+#
+# The following properties under oci are accessed by Helidon. Values
+# under oci.properties.* are read from ~/helidon/conf/examples.yaml.
+#
+app:
+ vault:
+ # Vault OCID (the vault you want to use for this example)
+ vault-ocid: "${oci.properties.vault-ocid}"
+ compartment-ocid: "${oci.properties.compartment-ocid}"
+ encryption-key-ocid: "${oci.properties.vault-key-ocid}"
+ signature-key-ocid: "${oci.properties.vault-rsa-key-ocid}"
+ cryptographic-endpoint: "${oci.properties.cryptographic-endpoint}"
diff --git a/examples/integrations/oci/vault-cdi/src/main/resources/logging.properties b/examples/integrations/oci/vault-cdi/src/main/resources/logging.properties
new file mode 100644
index 000000000..8e28f63f9
--- /dev/null
+++ b/examples/integrations/oci/vault-cdi/src/main/resources/logging.properties
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+io.helidon.level=INFO
+io.helidon.integrations.level=INFO
diff --git a/examples/integrations/oci/vault/README.md b/examples/integrations/oci/vault/README.md
new file mode 100644
index 000000000..42fa85511
--- /dev/null
+++ b/examples/integrations/oci/vault/README.md
@@ -0,0 +1,9 @@
+The vault example.
+
+The example requires OCI config in some default place like ``.oci/config``
+
+Build and run the example by
+```shell
+mvn package
+java -jar ./target/helidon-examples-integrations-oci-vault.jar
+```
diff --git a/examples/integrations/oci/vault/pom.xml b/examples/integrations/oci/vault/pom.xml
new file mode 100644
index 000000000..a46ab8376
--- /dev/null
+++ b/examples/integrations/oci/vault/pom.xml
@@ -0,0 +1,82 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+
+ io.helidon.examples.integrations.oci
+ helidon-examples-integrations-oci-vault
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration OCI Vault
+ Integration with OCI Vault.
+
+
+ io.helidon.examples.integrations.oci.vault.OciVaultMain
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-keymanagement
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-secrets
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-vault
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-common-httpclient-jersey3
+ runtime
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
+
diff --git a/examples/integrations/oci/vault/src/main/java/io/helidon/examples/integrations/oci/vault/OciVaultMain.java b/examples/integrations/oci/vault/src/main/java/io/helidon/examples/integrations/oci/vault/OciVaultMain.java
new file mode 100644
index 000000000..90566314b
--- /dev/null
+++ b/examples/integrations/oci/vault/src/main/java/io/helidon/examples/integrations/oci/vault/OciVaultMain.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.oci.vault;
+
+import java.io.IOException;
+
+import io.helidon.config.Config;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+
+import com.oracle.bmc.ConfigFileReader;
+import com.oracle.bmc.auth.AuthenticationDetailsProvider;
+import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider;
+import com.oracle.bmc.keymanagement.KmsCrypto;
+import com.oracle.bmc.keymanagement.KmsCryptoClient;
+import com.oracle.bmc.model.BmcException;
+import com.oracle.bmc.secrets.Secrets;
+import com.oracle.bmc.secrets.SecretsClient;
+import com.oracle.bmc.vault.Vaults;
+import com.oracle.bmc.vault.VaultsClient;
+
+import static io.helidon.config.ConfigSources.classpath;
+import static io.helidon.config.ConfigSources.file;
+
+/**
+ * Main class of the example.
+ * Boots a web server and provides REST API for Vault interactions.
+ */
+public final class OciVaultMain {
+ private OciVaultMain() {
+ }
+
+ /**
+ * Main method.
+ * @param args ignored
+ */
+ public static void main(String[] args) throws IOException {
+ LogConfig.configureRuntime();
+
+ // as I cannot share my configuration of OCI, let's combine the configuration
+ // from my home directory with the one compiled into the jar
+ // when running this example, you can either update the application.yaml in resources directory
+ // or use the same approach
+ Config config = buildConfig();
+
+ Config vaultConfig = config.get("oci.vault");
+ // the following three parameters are required
+ String vaultOcid = vaultConfig.get("vault-ocid").asString().get();
+ String compartmentOcid = vaultConfig.get("compartment-ocid").asString().get();
+ String encryptionKey = vaultConfig.get("encryption-key-ocid").asString().get();
+ String signatureKey = vaultConfig.get("signature-key-ocid").asString().get();
+ String cryptoEndpoint = vaultConfig.get("cryptographic-endpoint").asString().get();
+
+ // this requires OCI configuration in the usual place
+ // ~/.oci/config
+ AuthenticationDetailsProvider authProvider = new ConfigFileAuthenticationDetailsProvider(ConfigFileReader.parseDefault());
+
+ Secrets secrets = SecretsClient.builder().build(authProvider);
+ KmsCrypto crypto = KmsCryptoClient.builder()
+ .endpoint(cryptoEndpoint)
+ .build(authProvider);
+ Vaults vaults = VaultsClient.builder().build(authProvider);
+
+ WebServer server = WebServer.builder()
+ .routing(routing -> routing
+ .register("/vault", new VaultService(secrets,
+ vaults,
+ crypto,
+ vaultOcid,
+ compartmentOcid,
+ encryptionKey,
+ signatureKey))
+ .error(BmcException.class, (req, res, ex) -> res.status(
+ ex.getStatusCode()).send(ex.getMessage())))
+ .config(config.get("server"))
+ .build()
+ .start();
+
+ System.out.println("WEB server is up! http://localhost:" + server.port());
+
+ }
+
+ private static Config buildConfig() {
+ return Config.builder()
+ .sources(
+ // you can use this file to override the defaults that are built-in
+ file(System.getProperty("user.home") + "/helidon/conf/examples.yaml").optional(),
+ // in jar file (see src/main/resources/application.yaml)
+ classpath("application.yaml"))
+ .build();
+ }
+}
diff --git a/examples/integrations/oci/vault/src/main/java/io/helidon/examples/integrations/oci/vault/VaultService.java b/examples/integrations/oci/vault/src/main/java/io/helidon/examples/integrations/oci/vault/VaultService.java
new file mode 100644
index 000000000..7f47ac72c
--- /dev/null
+++ b/examples/integrations/oci/vault/src/main/java/io/helidon/examples/integrations/oci/vault/VaultService.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.oci.vault;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.function.Consumer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import io.helidon.common.Base64Value;
+import io.helidon.http.Status;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+import com.oracle.bmc.keymanagement.KmsCrypto;
+import com.oracle.bmc.keymanagement.model.DecryptDataDetails;
+import com.oracle.bmc.keymanagement.model.EncryptDataDetails;
+import com.oracle.bmc.keymanagement.model.SignDataDetails;
+import com.oracle.bmc.keymanagement.model.VerifyDataDetails;
+import com.oracle.bmc.keymanagement.requests.DecryptRequest;
+import com.oracle.bmc.keymanagement.requests.EncryptRequest;
+import com.oracle.bmc.keymanagement.requests.SignRequest;
+import com.oracle.bmc.keymanagement.requests.VerifyRequest;
+import com.oracle.bmc.keymanagement.responses.DecryptResponse;
+import com.oracle.bmc.keymanagement.responses.EncryptResponse;
+import com.oracle.bmc.keymanagement.responses.SignResponse;
+import com.oracle.bmc.keymanagement.responses.VerifyResponse;
+import com.oracle.bmc.secrets.Secrets;
+import com.oracle.bmc.secrets.model.Base64SecretBundleContentDetails;
+import com.oracle.bmc.secrets.model.SecretBundleContentDetails;
+import com.oracle.bmc.secrets.requests.GetSecretBundleRequest;
+import com.oracle.bmc.secrets.responses.GetSecretBundleResponse;
+import com.oracle.bmc.vault.Vaults;
+import com.oracle.bmc.vault.model.Base64SecretContentDetails;
+import com.oracle.bmc.vault.model.CreateSecretDetails;
+import com.oracle.bmc.vault.model.ScheduleSecretDeletionDetails;
+import com.oracle.bmc.vault.model.SecretContentDetails;
+import com.oracle.bmc.vault.requests.CreateSecretRequest;
+import com.oracle.bmc.vault.requests.ScheduleSecretDeletionRequest;
+import com.oracle.bmc.vault.responses.CreateSecretResponse;
+
+class VaultService implements HttpService {
+
+ private static final Logger LOGGER = Logger.getLogger(VaultService.class.getName());
+ private final Secrets secrets;
+ private final Vaults vaults;
+ private final KmsCrypto crypto;
+ private final String vaultOcid;
+ private final String compartmentOcid;
+ private final String encryptionKeyOcid;
+ private final String signatureKeyOcid;
+
+ VaultService(Secrets secrets,
+ Vaults vaults,
+ KmsCrypto crypto,
+ String vaultOcid,
+ String compartmentOcid,
+ String encryptionKeyOcid,
+ String signatureKeyOcid) {
+ this.secrets = secrets;
+ this.vaults = vaults;
+ this.crypto = crypto;
+ this.vaultOcid = vaultOcid;
+ this.compartmentOcid = compartmentOcid;
+ this.encryptionKeyOcid = encryptionKeyOcid;
+ this.signatureKeyOcid = signatureKeyOcid;
+ }
+
+ /**
+ * A service registers itself by updating the routine rules.
+ *
+ * @param rules the routing rules.
+ */
+ @Override
+ public void routing(HttpRules rules) {
+ rules.get("/encrypt/{text:.*}", this::encrypt)
+ .get("/decrypt/{text:.*}", this::decrypt)
+ .get("/sign/{text}", this::sign)
+ .post("/verify/{text}", this::verify)
+ .get("/secret/{id}", this::getSecret)
+ .post("/secret/{name}", this::createSecret)
+ .delete("/secret/{id}", this::deleteSecret);
+ }
+
+ private void getSecret(ServerRequest req, ServerResponse res) {
+ ociHandler(response -> {
+ GetSecretBundleResponse id = secrets.getSecretBundle(GetSecretBundleRequest.builder()
+ .secretId(req.path().pathParameters().get("id"))
+ .build());
+ SecretBundleContentDetails content = id.getSecretBundle().getSecretBundleContent();
+ if (content instanceof Base64SecretBundleContentDetails) {
+ // the only supported type
+ res.send(Base64Value.createFromEncoded(((Base64SecretBundleContentDetails) content).getContent())
+ .toDecodedString());
+ } else {
+ res.status(Status.INTERNAL_SERVER_ERROR_500).send("Invalid secret content type");
+ }
+ }, res);
+
+ }
+
+ private void deleteSecret(ServerRequest req, ServerResponse res) {
+ ociHandler(response -> {
+ // has to be for quite a long period of time - did not work with less than 30 days
+ Date deleteTime = Date.from(Instant.now().plus(30, ChronoUnit.DAYS));
+ String secretOcid = req.path().pathParameters().get("id");
+ vaults.scheduleSecretDeletion(ScheduleSecretDeletionRequest.builder()
+ .secretId(secretOcid)
+ .scheduleSecretDeletionDetails(ScheduleSecretDeletionDetails.builder()
+ .timeOfDeletion(deleteTime)
+ .build())
+ .build()
+ );
+ response.send(String.format("Secret %s was marked for deletion", secretOcid));
+ }, res);
+ }
+
+ private void createSecret(ServerRequest req, ServerResponse res) {
+ ociHandler(response -> {
+ String secretText = req.content().as(String.class);
+ SecretContentDetails content = Base64SecretContentDetails.builder()
+ .content(Base64Value.create(secretText).toBase64())
+ .build();
+ CreateSecretResponse vaultsSecret = vaults.createSecret(CreateSecretRequest.builder()
+ .createSecretDetails(CreateSecretDetails.builder()
+ .secretName(req.path().pathParameters().get("name"))
+ .vaultId(vaultOcid)
+ .compartmentId(compartmentOcid)
+ .keyId(encryptionKeyOcid)
+ .secretContent(content)
+ .build())
+ .build());
+ response.send(vaultsSecret.getSecret().getId());
+ }, res);
+ }
+
+ private void verify(ServerRequest req, ServerResponse res) {
+
+
+ ociHandler(response -> {
+ String text = req.path().pathParameters().get("text");
+ String signature = req.content().as(String.class);
+ VerifyDataDetails.SigningAlgorithm algorithm = VerifyDataDetails.SigningAlgorithm.Sha224RsaPkcsPss;
+ VerifyResponse verifyResponse = crypto.verify(VerifyRequest.builder()
+ .verifyDataDetails(VerifyDataDetails.builder()
+ .keyId(signatureKeyOcid)
+ .signingAlgorithm(algorithm)
+ .message(Base64Value.create(text).toBase64())
+ .signature(signature)
+ .build())
+ .build());
+ boolean valid = verifyResponse.getVerifiedData().getIsSignatureValid();
+ response.send(valid ? "Signature valid" : "Signature not valid");
+ }, res);
+ }
+
+ private void sign(ServerRequest req, ServerResponse res) {
+ ociHandler(response -> {
+ SignResponse signResponse = crypto.sign(SignRequest.builder()
+ .signDataDetails(SignDataDetails.builder()
+ .keyId(signatureKeyOcid)
+ .signingAlgorithm(SignDataDetails.SigningAlgorithm.Sha224RsaPkcsPss)
+ .message(Base64Value.create(req.path()
+ .pathParameters().get("text")).toBase64())
+ .build())
+ .build());
+ response.send(signResponse.getSignedData().getSignature());
+ }, res);
+ }
+
+ private void encrypt(ServerRequest req, ServerResponse res) {
+ ociHandler(response -> {
+ EncryptResponse encryptResponse = crypto.encrypt(EncryptRequest.builder()
+ .encryptDataDetails(EncryptDataDetails.builder()
+ .keyId(encryptionKeyOcid)
+ .plaintext(Base64Value.create(req.path()
+ .pathParameters().get("text")).toBase64())
+ .build())
+ .build());
+ response.send(encryptResponse.getEncryptedData().getCiphertext());
+ }, res);
+ }
+
+ private void decrypt(ServerRequest req, ServerResponse res) {
+ ociHandler(response -> {
+ DecryptResponse decryptResponse = crypto.decrypt(DecryptRequest.builder()
+ .decryptDataDetails(DecryptDataDetails.builder()
+ .keyId(encryptionKeyOcid)
+ .ciphertext(req.path()
+ .pathParameters().get("text"))
+ .build())
+ .build());
+ response.send(Base64Value.createFromEncoded(decryptResponse.getDecryptedData().getPlaintext())
+ .toDecodedString());
+ }, res);
+ }
+
+ private void ociHandler(Consumer consumer, ServerResponse response) {
+ try {
+ consumer.accept(response);
+ } catch (Throwable error) {
+ LOGGER.log(Level.WARNING, "OCI Exception", error);
+ response.status(Status.INTERNAL_SERVER_ERROR_500).send(error.getMessage());
+ }
+ }
+}
diff --git a/examples/integrations/oci/vault/src/main/java/io/helidon/examples/integrations/oci/vault/package-info.java b/examples/integrations/oci/vault/src/main/java/io/helidon/examples/integrations/oci/vault/package-info.java
new file mode 100644
index 000000000..5a5aae8d2
--- /dev/null
+++ b/examples/integrations/oci/vault/src/main/java/io/helidon/examples/integrations/oci/vault/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example of OCI Vault integration in a SE application.
+ */
+package io.helidon.examples.integrations.oci.vault;
diff --git a/examples/integrations/oci/vault/src/main/resources/application.yaml b/examples/integrations/oci/vault/src/main/resources/application.yaml
new file mode 100644
index 000000000..eaf0fff5b
--- /dev/null
+++ b/examples/integrations/oci/vault/src/main/resources/application.yaml
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 8080
+
+#
+# The following properties under oci are accessed by Helidon. Values
+# under oci.properties.* are read from ~/helidon/conf/examples.yaml.
+#
+oci:
+ vault:
+ # Vault OCID (the vault you want to use for this example
+ vault-ocid: "${oci.properties.vault-ocid}"
+ compartment-ocid: "${oci.properties.compartment-ocid}"
+ encryption-key-ocid: "${oci.properties.vault-key-ocid}"
+ signature-key-ocid: "${oci.properties.vault-rsa-key-ocid}"
+ cryptographic-endpoint: "${oci.properties.cryptographic-endpoint}"
diff --git a/examples/integrations/oci/vault/src/main/resources/logging.properties b/examples/integrations/oci/vault/src/main/resources/logging.properties
new file mode 100644
index 000000000..8e28f63f9
--- /dev/null
+++ b/examples/integrations/oci/vault/src/main/resources/logging.properties
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+io.helidon.level=INFO
+io.helidon.integrations.level=INFO
diff --git a/examples/integrations/pom.xml b/examples/integrations/pom.xml
new file mode 100644
index 000000000..13c652090
--- /dev/null
+++ b/examples/integrations/pom.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples
+ helidon-examples-project
+ 1.0.0-SNAPSHOT
+
+ io.helidon.examples.integrations
+ helidon-examples-integrations-project
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integrations
+ pom
+
+
+ cdi
+ micronaut
+ neo4j
+ micrometer
+ oci
+ vault
+ microstream
+
+
+
diff --git a/examples/integrations/vault/hcp-cdi/README.md b/examples/integrations/vault/hcp-cdi/README.md
new file mode 100644
index 000000000..764dabb49
--- /dev/null
+++ b/examples/integrations/vault/hcp-cdi/README.md
@@ -0,0 +1,26 @@
+HCP Vault Integration with Helidon APIs
+---
+
+This example expects an empty Vault. It uses the token to create all required resources.
+
+To run this example:
+
+1. Run a docker image with a known root token
+
+```shell
+docker run --cap-add=IPC_LOCK -e VAULT_DEV_ROOT_TOKEN_ID=myroot -d --name=vault -p8200:8200 vault
+```
+
+2. Build this application
+
+```shell
+mvn clean package
+```
+
+3. Start this application
+
+```shell
+java -jar ./target/helidon-examples-integrations-vault-hcp-cdi.jar
+```
+
+4. Exercise the endpoints
\ No newline at end of file
diff --git a/examples/integrations/vault/hcp-cdi/pom.xml b/examples/integrations/vault/hcp-cdi/pom.xml
new file mode 100644
index 000000000..a6e6833d8
--- /dev/null
+++ b/examples/integrations/vault/hcp-cdi/pom.xml
@@ -0,0 +1,100 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+
+ io.helidon.examples.integrations.vault
+ helidon-examples-integrations-vault-hcp-cdi
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration Vault CDI
+ CDI integration with Vault.
+
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile
+
+
+ io.helidon.integrations.vault
+ helidon-integrations-vault-cdi
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.integrations.vault.auths
+ helidon-integrations-vault-auths-token
+
+
+ io.helidon.integrations.vault.auths
+ helidon-integrations-vault-auths-approle
+
+
+ io.helidon.integrations.vault.secrets
+ helidon-integrations-vault-secrets-kv1
+
+
+ io.helidon.integrations.vault.secrets
+ helidon-integrations-vault-secrets-kv2
+
+
+ io.helidon.integrations.vault.secrets
+ helidon-integrations-vault-secrets-cubbyhole
+
+
+ io.helidon.integrations.vault.secrets
+ helidon-integrations-vault-secrets-transit
+
+
+ io.helidon.integrations.vault.sys
+ helidon-integrations-vault-sys
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
diff --git a/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/CubbyholeResource.java b/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/CubbyholeResource.java
new file mode 100644
index 000000000..a7943421b
--- /dev/null
+++ b/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/CubbyholeResource.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.vault.hcp.cdi;
+
+import java.util.Map;
+import java.util.Optional;
+
+import io.helidon.integrations.vault.Secret;
+import io.helidon.integrations.vault.secrets.cubbyhole.CreateCubbyhole;
+import io.helidon.integrations.vault.secrets.cubbyhole.CubbyholeSecrets;
+import io.helidon.integrations.vault.secrets.cubbyhole.DeleteCubbyhole;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * JAX-RS resource for Cubbyhole secrets engine operations.
+ */
+@Path("/cubbyhole")
+public class CubbyholeResource {
+ private final CubbyholeSecrets secrets;
+
+ @Inject
+ CubbyholeResource(CubbyholeSecrets secrets) {
+ this.secrets = secrets;
+ }
+
+ /**
+ * Create a secret from request entity, the name of the value is {@code secret}.
+ *
+ * @param path path of the secret taken from request path
+ * @param secret secret from the entity
+ * @return response
+ */
+ @POST
+ @Path("/secrets/{path: .*}")
+ public Response createSecret(@PathParam("path") String path, String secret) {
+ CreateCubbyhole.Response response = secrets.create(path, Map.of("secret", secret));
+
+ return Response.ok()
+ .entity("Created secret on path: " + path + ", key is \"secret\", original status: " + response.status().code())
+ .build();
+ }
+
+ /**
+ * Delete the secret on a specified path.
+ *
+ * @param path path of the secret taken from request path
+ * @return response
+ */
+ @DELETE
+ @Path("/secrets/{path: .*}")
+ public Response deleteSecret(@PathParam("path") String path) {
+ DeleteCubbyhole.Response response = secrets.delete(path);
+
+ return Response.ok()
+ .entity("Deleted secret on path: " + path + ". Original status: " + response.status().code())
+ .build();
+ }
+
+ /**
+ * Get the secret on a specified path.
+ *
+ * @param path path of the secret taken from request path
+ * @return response
+ */
+ @GET
+ @Path("/secrets/{path: .*}")
+ public Response getSecret(@PathParam("path") String path) {
+ Optional secret = secrets.get(path);
+
+ if (secret.isPresent()) {
+ return Response.ok()
+ .entity("Secret: " + secret.get().values().toString())
+ .build();
+ } else {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+ }
+}
diff --git a/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/Kv1Resource.java b/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/Kv1Resource.java
new file mode 100644
index 000000000..50f03a997
--- /dev/null
+++ b/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/Kv1Resource.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.vault.hcp.cdi;
+
+import java.util.Map;
+import java.util.Optional;
+
+import io.helidon.integrations.vault.Secret;
+import io.helidon.integrations.vault.secrets.kv1.CreateKv1;
+import io.helidon.integrations.vault.secrets.kv1.DeleteKv1;
+import io.helidon.integrations.vault.secrets.kv1.Kv1Secrets;
+import io.helidon.integrations.vault.sys.DisableEngine;
+import io.helidon.integrations.vault.sys.EnableEngine;
+import io.helidon.integrations.vault.sys.Sys;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * JAX-RS resource for Key/Value version 1 secrets engine operations.
+ */
+@Path("/kv1")
+public class Kv1Resource {
+ private final Sys sys;
+ private final Kv1Secrets secrets;
+
+ @Inject
+ Kv1Resource(Sys sys, Kv1Secrets secrets) {
+ this.sys = sys;
+ this.secrets = secrets;
+ }
+
+ /**
+ * Enable the secrets engine on the default path.
+ *
+ * @return response
+ */
+ @Path("/engine")
+ @GET
+ public Response enableEngine() {
+ EnableEngine.Response response = sys.enableEngine(Kv1Secrets.ENGINE);
+
+ return Response.ok()
+ .entity("Key/value version 1 secret engine is now enabled. Original status: " + response.status().code())
+ .build();
+ }
+
+ /**
+ * Disable the secrets engine on the default path.
+ * @return response
+ */
+ @Path("/engine")
+ @DELETE
+ public Response disableEngine() {
+ DisableEngine.Response response = sys.disableEngine(Kv1Secrets.ENGINE);
+ return Response.ok()
+ .entity("Key/value version 1 secret engine is now disabled. Original status: " + response.status().code())
+ .build();
+ }
+
+ /**
+ * Create a secret from request entity, the name of the value is {@code secret}.
+ *
+ * @param path path of the secret taken from request path
+ * @param secret secret from the entity
+ * @return response
+ */
+ @POST
+ @Path("/secrets/{path: .*}")
+ public Response createSecret(@PathParam("path") String path, String secret) {
+ CreateKv1.Response response = secrets.create(path, Map.of("secret", secret));
+
+ return Response.ok()
+ .entity("Created secret on path: " + path + ", key is \"secret\", original status: " + response.status().code())
+ .build();
+ }
+
+ /**
+ * Delete the secret on a specified path.
+ *
+ * @param path path of the secret taken from request path
+ * @return response
+ */
+ @DELETE
+ @Path("/secrets/{path: .*}")
+ public Response deleteSecret(@PathParam("path") String path) {
+ DeleteKv1.Response response = secrets.delete(path);
+
+ return Response.ok()
+ .entity("Deleted secret on path: " + path + ". Original status: " + response.status().code())
+ .build();
+ }
+
+ /**
+ * Get the secret on a specified path.
+ *
+ * @param path path of the secret taken from request path
+ * @return response
+ */
+ @GET
+ @Path("/secrets/{path: .*}")
+ public Response getSecret(@PathParam("path") String path) {
+ Optional secret = secrets.get(path);
+
+ if (secret.isPresent()) {
+ Secret kv1Secret = secret.get();
+ return Response.ok()
+ .entity("Secret: " + secret.get().values().toString())
+ .build();
+ } else {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+ }
+}
diff --git a/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/Kv2Resource.java b/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/Kv2Resource.java
new file mode 100644
index 000000000..095d23997
--- /dev/null
+++ b/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/Kv2Resource.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.vault.hcp.cdi;
+
+import java.util.Map;
+import java.util.Optional;
+
+import io.helidon.integrations.vault.secrets.kv2.CreateKv2;
+import io.helidon.integrations.vault.secrets.kv2.DeleteAllKv2;
+import io.helidon.integrations.vault.secrets.kv2.Kv2Secret;
+import io.helidon.integrations.vault.secrets.kv2.Kv2Secrets;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * JAX-RS resource for Key/Value version 2 secrets engine operations.
+ */
+@Path("/kv2")
+public class Kv2Resource {
+ private final Kv2Secrets secrets;
+
+ @Inject
+ Kv2Resource(Kv2Secrets secrets) {
+ this.secrets = secrets;
+ }
+
+ /**
+ * Create a secret from request entity, the name of the value is {@code secret}.
+ *
+ * @param path path of the secret taken from request path
+ * @param secret secret from the entity
+ * @return response
+ */
+ @POST
+ @Path("/secrets/{path: .*}")
+ public Response createSecret(@PathParam("path") String path, String secret) {
+ CreateKv2.Response response = secrets.create(path, Map.of("secret", secret));
+
+ return Response.ok()
+ .entity("Created secret on path: " + path + ", key is \"secret\", original status: " + response.status().code())
+ .build();
+ }
+
+ /**
+ * Delete the secret on a specified path.
+ *
+ * @param path path of the secret taken from request path
+ * @return response
+ */
+ @DELETE
+ @Path("/secrets/{path: .*}")
+ public Response deleteSecret(@PathParam("path") String path) {
+ DeleteAllKv2.Response response = secrets.deleteAll(path);
+
+ return Response.ok()
+ .entity("Deleted secret on path: " + path + ". Original status: " + response.status().code())
+ .build();
+ }
+
+ /**
+ * Get the secret on a specified path.
+ *
+ * @param path path of the secret taken from request path
+ * @return response
+ */
+ @GET
+ @Path("/secrets/{path: .*}")
+ public Response getSecret(@PathParam("path") String path) {
+
+ Optional secret = secrets.get(path);
+
+ if (secret.isPresent()) {
+ Kv2Secret kv2Secret = secret.get();
+ return Response.ok()
+ .entity("Version " + kv2Secret.metadata().version() + ", secret: " + kv2Secret.values().toString())
+ .build();
+ } else {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+ }
+}
diff --git a/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/TransitResource.java b/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/TransitResource.java
new file mode 100644
index 000000000..358ab8f9f
--- /dev/null
+++ b/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/TransitResource.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.vault.hcp.cdi;
+
+import io.helidon.common.Base64Value;
+import io.helidon.integrations.vault.secrets.transit.CreateKey;
+import io.helidon.integrations.vault.secrets.transit.Decrypt;
+import io.helidon.integrations.vault.secrets.transit.DeleteKey;
+import io.helidon.integrations.vault.secrets.transit.Encrypt;
+import io.helidon.integrations.vault.secrets.transit.Hmac;
+import io.helidon.integrations.vault.secrets.transit.Sign;
+import io.helidon.integrations.vault.secrets.transit.TransitSecrets;
+import io.helidon.integrations.vault.secrets.transit.UpdateKeyConfig;
+import io.helidon.integrations.vault.secrets.transit.Verify;
+import io.helidon.integrations.vault.sys.DisableEngine;
+import io.helidon.integrations.vault.sys.EnableEngine;
+import io.helidon.integrations.vault.sys.Sys;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * JAX-RS resource for Transit secrets engine operations.
+ */
+@Path("/transit")
+public class TransitResource {
+ private static final String ENCRYPTION_KEY = "encryption-key";
+ private static final String SIGNATURE_KEY = "signature-key";
+
+ private final Sys sys;
+ private final TransitSecrets secrets;
+
+ @Inject
+ TransitResource(Sys sys, TransitSecrets secrets) {
+ this.sys = sys;
+ this.secrets = secrets;
+ }
+
+ /**
+ * Enable the secrets engine on the default path.
+ *
+ * @return response
+ */
+ @Path("/engine")
+ @GET
+ public Response enableEngine() {
+ EnableEngine.Response response = sys.enableEngine(TransitSecrets.ENGINE);
+
+ return Response.ok()
+ .entity("Transit secret engine is now enabled. Original status: " + response.status().code())
+ .build();
+ }
+
+ /**
+ * Disable the secrets engine on the default path.
+ * @return response
+ */
+ @Path("/engine")
+ @DELETE
+ public Response disableEngine() {
+ DisableEngine.Response response = sys.disableEngine(TransitSecrets.ENGINE);
+
+ return Response.ok()
+ .entity("Transit secret engine is now disabled. Original status: " + response.status())
+ .build();
+ }
+
+ /**
+ * Create the encrypting and signature keys.
+ *
+ * @return response
+ */
+ @Path("/keys")
+ @GET
+ public Response createKeys() {
+ secrets.createKey(CreateKey.Request.builder()
+ .name(ENCRYPTION_KEY));
+
+ secrets.createKey(CreateKey.Request.builder()
+ .name(SIGNATURE_KEY)
+ .type("rsa-2048"));
+
+ return Response.ok()
+ .entity("Created encryption (and HMAC), and signature keys")
+ .build();
+ }
+
+ /**
+ * Delete the encryption and signature keys.
+ *
+ * @return response
+ */
+ @Path("/keys")
+ @DELETE
+ public Response deleteKeys() {
+ // we must first enable deletion of the key (by default it cannot be deleted)
+ secrets.updateKeyConfig(UpdateKeyConfig.Request.builder()
+ .name(ENCRYPTION_KEY)
+ .allowDeletion(true));
+
+ secrets.updateKeyConfig(UpdateKeyConfig.Request.builder()
+ .name(SIGNATURE_KEY)
+ .allowDeletion(true));
+
+ secrets.deleteKey(DeleteKey.Request.create(ENCRYPTION_KEY));
+ secrets.deleteKey(DeleteKey.Request.create(SIGNATURE_KEY));
+
+ return Response.ok()
+ .entity("Deleted encryption (and HMAC), and signature keys")
+ .build();
+ }
+
+ /**
+ * Encrypt a secret.
+ *
+ * @param secret provided as part of the path
+ * @return cipher text
+ */
+ @Path("/encrypt/{secret: .*}")
+ @GET
+ public String encryptSecret(@PathParam("secret") String secret) {
+ return secrets.encrypt(Encrypt.Request.builder()
+ .encryptionKeyName(ENCRYPTION_KEY)
+ .data(Base64Value.create(secret)))
+ .encrypted()
+ .cipherText();
+ }
+
+ /**
+ * Decrypt a secret.
+ *
+ * @param cipherText provided as part of the path
+ * @return decrypted secret text
+ */
+ @Path("/decrypt/{cipherText: .*}")
+ @GET
+ public String decryptSecret(@PathParam("cipherText") String cipherText) {
+ return secrets.decrypt(Decrypt.Request.builder()
+ .encryptionKeyName(ENCRYPTION_KEY)
+ .cipherText(cipherText))
+ .decrypted()
+ .toDecodedString();
+ }
+
+ /**
+ * Create an HMAC for text.
+ *
+ * @param text text to do HMAC for
+ * @return hmac string that can be used to {@link #verifyHmac(String, String)}
+ */
+ @Path("/hmac/{text}")
+ @GET
+ public String hmac(@PathParam("text") String text) {
+ return secrets.hmac(Hmac.Request.builder()
+ .hmacKeyName(ENCRYPTION_KEY)
+ .data(Base64Value.create(text)))
+ .hmac();
+ }
+
+ /**
+ * Create a signature for text.
+ *
+ * @param text text to sign
+ * @return signature string that can be used to {@link #verifySignature(String, String)}
+ */
+ @Path("/sign/{text}")
+ @GET
+ public String sign(@PathParam("text") String text) {
+ return secrets.sign(Sign.Request.builder()
+ .signatureKeyName(SIGNATURE_KEY)
+ .data(Base64Value.create(text)))
+ .signature();
+ }
+
+ /**
+ * Verify HMAC.
+ *
+ * @param secret secret that was used to {@link #hmac(String)}
+ * @param hmac HMAC text
+ * @return {@code HMAC Valid} or {@code HMAC Invalid}
+ */
+ @Path("/verify/hmac/{secret}/{hmac: .*}")
+ @GET
+ public String verifyHmac(@PathParam("secret") String secret, @PathParam("hmac") String hmac) {
+ boolean isValid = secrets.verify(Verify.Request.builder()
+ .digestKeyName(ENCRYPTION_KEY)
+ .data(Base64Value.create(secret))
+ .hmac(hmac))
+ .isValid();
+
+ return (isValid ? "HMAC Valid" : "HMAC Invalid");
+ }
+
+ /**
+ * Verify signature.
+ *
+ * @param secret secret that was used to {@link #sign(String)}
+ * @param signature signature
+ * @return {@code Signature Valid} or {@code Signature Invalid}
+ */
+ @Path("/verify/sign/{secret}/{signature: .*}")
+ @GET
+ public String verifySignature(@PathParam("secret") String secret, @PathParam("signature") String signature) {
+ boolean isValid = secrets.verify(Verify.Request.builder()
+ .digestKeyName(SIGNATURE_KEY)
+ .data(Base64Value.create(secret))
+ .signature(signature))
+ .isValid();
+
+ return (isValid ? "Signature Valid" : "Signature Invalid");
+ }
+}
diff --git a/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/VaultCdiMain.java b/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/VaultCdiMain.java
new file mode 100644
index 000000000..0d14ae1c4
--- /dev/null
+++ b/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/VaultCdiMain.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.vault.hcp.cdi;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import io.helidon.config.yaml.mp.YamlMpConfigSource;
+import io.helidon.microprofile.cdi.Main;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
+/**
+ * Main class of example.
+ */
+public final class VaultCdiMain {
+ private VaultCdiMain() {
+ }
+
+ /**
+ * Main method of example.
+ *
+ * @param args ignored
+ */
+ public static void main(String[] args) {
+ ConfigProviderResolver configProvider = ConfigProviderResolver.instance();
+
+ Config mpConfig = configProvider.getBuilder()
+ .addDefaultSources()
+ .withSources(examplesConfig())
+ .addDiscoveredSources()
+ .addDiscoveredConverters()
+ .build();
+
+ // configure
+ configProvider.registerConfig(mpConfig, null);
+
+ // start CDI
+ Main.main(args);
+ }
+
+ private static ConfigSource[] examplesConfig() {
+ Path path = Paths.get(System.getProperty("user.home") + "/helidon/conf/examples.yaml");
+ if (Files.exists(path)) {
+ return new ConfigSource[] {YamlMpConfigSource.create(path)};
+ }
+ return new ConfigSource[0];
+ }
+}
diff --git a/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/package-info.java b/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/package-info.java
new file mode 100644
index 000000000..34e9215f2
--- /dev/null
+++ b/examples/integrations/vault/hcp-cdi/src/main/java/io/helidon/examples/integrations/vault/hcp/cdi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example of Helidon integration with Hashicorp Vault within CDI.
+ */
+package io.helidon.examples.integrations.vault.hcp.cdi;
diff --git a/examples/integrations/vault/hcp-cdi/src/main/resources/META-INF/beans.xml b/examples/integrations/vault/hcp-cdi/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..e149ced7d
--- /dev/null
+++ b/examples/integrations/vault/hcp-cdi/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/integrations/vault/hcp-cdi/src/main/resources/application.yaml b/examples/integrations/vault/hcp-cdi/src/main/resources/application.yaml
new file mode 100644
index 000000000..67fdf52e8
--- /dev/null
+++ b/examples/integrations/vault/hcp-cdi/src/main/resources/application.yaml
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server.port: 8080
+
+vault:
+ default:
+ address: "http://localhost:8200"
+ token: "myroot"
+ auth:
+ app-role:
+ enabled: false
+ token:
+ enabled: true
diff --git a/examples/integrations/vault/hcp-cdi/src/main/resources/logging.properties b/examples/integrations/vault/hcp-cdi/src/main/resources/logging.properties
new file mode 100644
index 000000000..8e28f63f9
--- /dev/null
+++ b/examples/integrations/vault/hcp-cdi/src/main/resources/logging.properties
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+io.helidon.level=INFO
+io.helidon.integrations.level=INFO
diff --git a/examples/integrations/vault/hcp/README.md b/examples/integrations/vault/hcp/README.md
new file mode 100644
index 000000000..1bcbac31d
--- /dev/null
+++ b/examples/integrations/vault/hcp/README.md
@@ -0,0 +1,26 @@
+HCP Vault Integration
+---
+
+This example expects an empty Vault. It uses the token to create all required resources.
+
+To run this example:
+
+1. Run a docker image with a known root token
+
+```shell
+docker run --cap-add=IPC_LOCK -e VAULT_DEV_ROOT_TOKEN_ID=myroot -d --name=vault -p8200:8200 vault
+```
+
+2. Build this application
+
+```shell
+mvn clean package
+```
+
+3. Start this application
+
+```shell
+java -jar ./target/helidon-examples-integrations-vault-hcp.jar
+```
+
+4. Exercise the endpoints
\ No newline at end of file
diff --git a/examples/integrations/vault/hcp/pom.xml b/examples/integrations/vault/hcp/pom.xml
new file mode 100644
index 000000000..c4c44fd7a
--- /dev/null
+++ b/examples/integrations/vault/hcp/pom.xml
@@ -0,0 +1,99 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+
+ io.helidon.examples.integrations.vault
+ helidon-examples-integrations-vault-hcp
+ 1.0.0-SNAPSHOT
+ Helidon Examples Integration Vault
+ Helidon integration with Vault.
+
+
+ io.helidon.examples.integrations.vault.hcp.VaultMain
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.integrations.vault
+ helidon-integrations-vault
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.integrations.vault.auths
+ helidon-integrations-vault-auths-token
+
+
+ io.helidon.integrations.vault.auths
+ helidon-integrations-vault-auths-approle
+
+
+ io.helidon.integrations.vault.auths
+ helidon-integrations-vault-auths-k8s
+
+
+ io.helidon.integrations.vault.secrets
+ helidon-integrations-vault-secrets-kv1
+
+
+ io.helidon.integrations.vault.secrets
+ helidon-integrations-vault-secrets-kv2
+
+
+ io.helidon.integrations.vault.secrets
+ helidon-integrations-vault-secrets-cubbyhole
+
+
+ io.helidon.integrations.vault.secrets
+ helidon-integrations-vault-secrets-transit
+
+
+ io.helidon.integrations.vault.sys
+ helidon-integrations-vault-sys
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/AppRoleExample.java b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/AppRoleExample.java
new file mode 100644
index 000000000..1298561c2
--- /dev/null
+++ b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/AppRoleExample.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.vault.hcp;
+
+import java.time.Duration;
+import java.util.Map;
+import java.util.Optional;
+
+import io.helidon.config.Config;
+import io.helidon.integrations.vault.Vault;
+import io.helidon.integrations.vault.auths.approle.AppRoleAuth;
+import io.helidon.integrations.vault.auths.approle.AppRoleVaultAuth;
+import io.helidon.integrations.vault.auths.approle.CreateAppRole;
+import io.helidon.integrations.vault.auths.approle.GenerateSecretId;
+import io.helidon.integrations.vault.secrets.kv2.Kv2Secret;
+import io.helidon.integrations.vault.secrets.kv2.Kv2Secrets;
+import io.helidon.integrations.vault.sys.EnableAuth;
+import io.helidon.integrations.vault.sys.Sys;
+
+class AppRoleExample {
+ private static final String SECRET_PATH = "approle/example/secret";
+ private static final String ROLE_NAME = "approle_role";
+ private static final String POLICY_NAME = "approle_policy";
+ private static final String CUSTOM_APP_ROLE_PATH = "customapprole";
+
+ private final Vault tokenVault;
+ private final Config config;
+ private final Sys sys;
+
+ private Vault appRoleVault;
+
+ AppRoleExample(Vault tokenVault, Config config) {
+ this.tokenVault = tokenVault;
+ this.config = config;
+
+ this.sys = tokenVault.sys(Sys.API);
+ }
+
+ public String run() {
+ /*
+ The following tasks must be run before we authenticate
+ */
+ enableAppRoleAuth();
+ workWithSecrets();
+ disableAppRoleAuth();
+ return "AppRole example finished successfully.";
+ }
+
+ private void workWithSecrets() {
+ Kv2Secrets secrets = appRoleVault.secrets(Kv2Secrets.ENGINE);
+ secrets.create(SECRET_PATH, Map.of("secret-key", "secretValue",
+ "secret-user", "username"));
+ Optional secret = secrets.get(SECRET_PATH);
+ if (secret.isPresent()) {
+ Kv2Secret kv2Secret = secret.get();
+ System.out.println("appRole first secret: " + kv2Secret.value("secret-key"));
+ System.out.println("appRole second secret: " + kv2Secret.value("secret-user"));
+ } else {
+ System.out.println("appRole secret not found");
+ }
+ secrets.deleteAll(SECRET_PATH);
+ }
+
+ private void disableAppRoleAuth() {
+ sys.deletePolicy(POLICY_NAME);
+ sys.disableAuth(CUSTOM_APP_ROLE_PATH);
+ }
+
+ private void enableAppRoleAuth() {
+
+ // enable the method
+ sys.enableAuth(EnableAuth.Request.builder()
+ .auth(AppRoleAuth.AUTH_METHOD)
+ // must be aligned with path configured in application.yaml
+ .path(CUSTOM_APP_ROLE_PATH));
+
+ // add policy
+ sys.createPolicy(POLICY_NAME, VaultPolicy.POLICY);
+
+ tokenVault.auth(AppRoleAuth.AUTH_METHOD, CUSTOM_APP_ROLE_PATH)
+ .createAppRole(CreateAppRole.Request.builder()
+ .roleName(ROLE_NAME)
+ .addTokenPolicy(POLICY_NAME)
+ .tokenExplicitMaxTtl(Duration.ofMinutes(1)));
+
+ String roleId = tokenVault.auth(AppRoleAuth.AUTH_METHOD, CUSTOM_APP_ROLE_PATH)
+ .readRoleId(ROLE_NAME)
+ .orElseThrow();
+
+
+ GenerateSecretId.Response response = tokenVault.auth(AppRoleAuth.AUTH_METHOD, CUSTOM_APP_ROLE_PATH)
+ .generateSecretId(GenerateSecretId.Request.builder()
+ .roleName(ROLE_NAME)
+ .addMetadata("name", "helidon"));
+
+ String secretId = response.secretId();
+
+ System.out.println("roleId: " + roleId);
+ System.out.println("secretId: " + secretId);
+ appRoleVault = Vault.builder()
+ .config(config)
+ .addVaultAuth(AppRoleVaultAuth.builder()
+ .path(CUSTOM_APP_ROLE_PATH)
+ .appRoleId(roleId)
+ .secretId(secretId)
+ .build())
+ .build();
+ }
+}
diff --git a/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/CubbyholeService.java b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/CubbyholeService.java
new file mode 100644
index 000000000..71c15ff68
--- /dev/null
+++ b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/CubbyholeService.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.vault.hcp;
+
+import java.util.Map;
+import java.util.Optional;
+
+import io.helidon.http.Status;
+import io.helidon.integrations.vault.Secret;
+import io.helidon.integrations.vault.secrets.cubbyhole.CubbyholeSecrets;
+import io.helidon.integrations.vault.sys.Sys;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+class CubbyholeService implements HttpService {
+ private final Sys sys;
+ private final CubbyholeSecrets secrets;
+
+ CubbyholeService(Sys sys, CubbyholeSecrets secrets) {
+ this.sys = sys;
+ this.secrets = secrets;
+ }
+
+ @Override
+ public void routing(HttpRules rules) {
+ rules.get("/create", this::createSecrets)
+ .get("/secrets/{path:.*}", this::getSecret);
+ }
+
+ private void createSecrets(ServerRequest req, ServerResponse res) {
+ secrets.create("first/secret", Map.of("key", "secretValue"));
+ res.send("Created secret on path /first/secret");
+ }
+
+ private void getSecret(ServerRequest req, ServerResponse res) {
+ String path = req.path().pathParameters().get("path");
+ Optional secret = secrets.get(path);
+ if (secret.isPresent()) {
+ // using toString so we do not need to depend on JSON-B
+ res.send(secret.get().values().toString());
+ } else {
+ res.status(Status.NOT_FOUND_404);
+ res.send();
+ }
+ }
+}
diff --git a/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/K8sExample.java b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/K8sExample.java
new file mode 100644
index 000000000..7462c8a01
--- /dev/null
+++ b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/K8sExample.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.vault.hcp;
+
+import java.util.Map;
+import java.util.Optional;
+
+import io.helidon.config.Config;
+import io.helidon.integrations.vault.Vault;
+import io.helidon.integrations.vault.auths.k8s.ConfigureK8s;
+import io.helidon.integrations.vault.auths.k8s.CreateRole;
+import io.helidon.integrations.vault.auths.k8s.K8sAuth;
+import io.helidon.integrations.vault.secrets.kv2.Kv2Secret;
+import io.helidon.integrations.vault.secrets.kv2.Kv2Secrets;
+import io.helidon.integrations.vault.sys.Sys;
+
+class K8sExample {
+ private static final String SECRET_PATH = "k8s/example/secret";
+ private static final String POLICY_NAME = "k8s_policy";
+
+ private final Vault tokenVault;
+ private final String k8sAddress;
+ private final Config config;
+ private final Sys sys;
+
+ private Vault k8sVault;
+
+ K8sExample(Vault tokenVault, Config config) {
+ this.tokenVault = tokenVault;
+ this.sys = tokenVault.sys(Sys.API);
+ this.k8sAddress = config.get("cluster-address").asString().get();
+ this.config = config;
+ }
+
+ public String run() {
+ /*
+ The following tasks must be run before we authenticate
+ */
+ enableK8sAuth();
+ // Now we can login using k8s - must run within a k8s cluster (or you need the k8s configuration files locally)
+ workWithSecrets();
+ // Now back to token based Vault, as we will clean up
+ disableK8sAuth();
+ return "k8s example finished successfully.";
+ }
+
+ private void workWithSecrets() {
+ Kv2Secrets secrets = k8sVault.secrets(Kv2Secrets.ENGINE);
+
+ secrets.create(SECRET_PATH, Map.of("secret-key", "secretValue",
+ "secret-user", "username"));
+
+ Optional secret = secrets.get(SECRET_PATH);
+ if (secret.isPresent()) {
+ Kv2Secret kv2Secret = secret.get();
+ System.out.println("k8s first secret: " + kv2Secret.value("secret-key"));
+ System.out.println("k8s second secret: " + kv2Secret.value("secret-user"));
+ } else {
+ System.out.println("k8s secret not found");
+ }
+ secrets.deleteAll(SECRET_PATH);
+ }
+
+ private void disableK8sAuth() {
+ sys.deletePolicy(POLICY_NAME);
+ sys.disableAuth(K8sAuth.AUTH_METHOD.defaultPath());
+ }
+
+ private void enableK8sAuth() {
+ // enable the method
+ sys.enableAuth(K8sAuth.AUTH_METHOD);
+ sys.createPolicy(POLICY_NAME, VaultPolicy.POLICY);
+ tokenVault.auth(K8sAuth.AUTH_METHOD)
+ .configure(ConfigureK8s.Request.builder()
+ .address(k8sAddress));
+ tokenVault.auth(K8sAuth.AUTH_METHOD)
+ // this must be the same role name as is defined in application.yaml
+ .createRole(CreateRole.Request.builder()
+ .roleName("my-role")
+ .addBoundServiceAccountName("*")
+ .addBoundServiceAccountNamespace("default")
+ .addTokenPolicy(POLICY_NAME));
+ k8sVault = Vault.create(config);
+ }
+}
diff --git a/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/Kv1Service.java b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/Kv1Service.java
new file mode 100644
index 000000000..1b0c9f998
--- /dev/null
+++ b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/Kv1Service.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.vault.hcp;
+
+import java.util.Map;
+import java.util.Optional;
+
+import io.helidon.http.Status;
+import io.helidon.integrations.vault.Secret;
+import io.helidon.integrations.vault.secrets.kv1.Kv1Secrets;
+import io.helidon.integrations.vault.sys.Sys;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+class Kv1Service implements HttpService {
+ private final Sys sys;
+ private final Kv1Secrets secrets;
+
+ Kv1Service(Sys sys, Kv1Secrets secrets) {
+ this.sys = sys;
+ this.secrets = secrets;
+ }
+
+ @Override
+ public void routing(HttpRules rules) {
+ rules.get("/enable", this::enableEngine)
+ .get("/create", this::createSecrets)
+ .get("/secrets/{path:.*}", this::getSecret)
+ .delete("/secrets/{path:.*}", this::deleteSecret)
+ .get("/disable", this::disableEngine);
+ }
+
+ private void disableEngine(ServerRequest req, ServerResponse res) {
+ sys.disableEngine(Kv1Secrets.ENGINE);
+ res.send("KV1 Secret engine disabled");
+ }
+
+ private void enableEngine(ServerRequest req, ServerResponse res) {
+ sys.enableEngine(Kv1Secrets.ENGINE);
+ res.send("KV1 Secret engine enabled");
+ }
+
+ private void createSecrets(ServerRequest req, ServerResponse res) {
+ secrets.create("first/secret", Map.of("key", "secretValue"));
+ res.send("Created secret on path /first/secret");
+ }
+
+ private void deleteSecret(ServerRequest req, ServerResponse res) {
+ String path = req.path().pathParameters().get("path");
+ secrets.delete(path);
+ res.send("Deleted secret on path " + path);
+ }
+
+ private void getSecret(ServerRequest req, ServerResponse res) {
+ String path = req.path().pathParameters().get("path");
+
+ Optional secret = secrets.get(path);
+ if (secret.isPresent()) {
+ // using toString so we do not need to depend on JSON-B
+ res.send(secret.get().values().toString());
+ } else {
+ res.status(Status.NOT_FOUND_404);
+ res.send();
+ }
+ }
+}
diff --git a/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/Kv2Service.java b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/Kv2Service.java
new file mode 100644
index 000000000..c46b510ac
--- /dev/null
+++ b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/Kv2Service.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.vault.hcp;
+
+import java.util.Map;
+import java.util.Optional;
+
+import io.helidon.http.Status;
+import io.helidon.integrations.vault.secrets.kv2.Kv2Secret;
+import io.helidon.integrations.vault.secrets.kv2.Kv2Secrets;
+import io.helidon.integrations.vault.sys.Sys;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+class Kv2Service implements HttpService {
+ private final Sys sys;
+ private final Kv2Secrets secrets;
+
+ Kv2Service(Sys sys, Kv2Secrets secrets) {
+ this.sys = sys;
+ this.secrets = secrets;
+ }
+
+ @Override
+ public void routing(HttpRules rules) {
+ rules.get("/create", this::createSecrets)
+ .get("/secrets/{path:.*}", this::getSecret)
+ .delete("/secrets/{path:.*}", this::deleteSecret);
+ }
+
+ private void createSecrets(ServerRequest req, ServerResponse res) {
+ secrets.create("first/secret", Map.of("key", "secretValue"));
+ res.send("Created secret on path /first/secret");
+ }
+
+ private void deleteSecret(ServerRequest req, ServerResponse res) {
+ String path = req.path().pathParameters().get("path");
+ secrets.deleteAll(path);
+ res.send("Deleted secret on path " + path);
+ }
+
+ private void getSecret(ServerRequest req, ServerResponse res) {
+ String path = req.path().pathParameters().get("path");
+
+ Optional secret = secrets.get(path);
+ if (secret.isPresent()) {
+ // using toString so we do not need to depend on JSON-B
+ Kv2Secret kv2Secret = secret.get();
+ res.send("Version " + kv2Secret.metadata().version() + ", secret: " + kv2Secret.values().toString());
+ } else {
+ res.status(Status.NOT_FOUND_404);
+ res.send();
+ }
+ }
+}
diff --git a/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/TransitService.java b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/TransitService.java
new file mode 100644
index 000000000..745d48e6b
--- /dev/null
+++ b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/TransitService.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.vault.hcp;
+
+import java.util.List;
+
+import io.helidon.common.Base64Value;
+import io.helidon.integrations.vault.secrets.transit.CreateKey;
+import io.helidon.integrations.vault.secrets.transit.Decrypt;
+import io.helidon.integrations.vault.secrets.transit.DecryptBatch;
+import io.helidon.integrations.vault.secrets.transit.DeleteKey;
+import io.helidon.integrations.vault.secrets.transit.Encrypt;
+import io.helidon.integrations.vault.secrets.transit.EncryptBatch;
+import io.helidon.integrations.vault.secrets.transit.Hmac;
+import io.helidon.integrations.vault.secrets.transit.Sign;
+import io.helidon.integrations.vault.secrets.transit.TransitSecrets;
+import io.helidon.integrations.vault.secrets.transit.UpdateKeyConfig;
+import io.helidon.integrations.vault.secrets.transit.Verify;
+import io.helidon.integrations.vault.sys.Sys;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+class TransitService implements HttpService {
+ private static final String ENCRYPTION_KEY = "encryption-key";
+ private static final String SIGNATURE_KEY = "signature-key";
+ private static final Base64Value SECRET_STRING = Base64Value.create("Hello World");
+ private final Sys sys;
+ private final TransitSecrets secrets;
+
+ TransitService(Sys sys, TransitSecrets secrets) {
+ this.sys = sys;
+ this.secrets = secrets;
+ }
+
+ @Override
+ public void routing(HttpRules rules) {
+ rules.get("/enable", this::enableEngine)
+ .get("/keys", this::createKeys)
+ .delete("/keys", this::deleteKeys)
+ .get("/batch", this::batch)
+ .get("/encrypt/{text:.*}", this::encryptSecret)
+ .get("/decrypt/{text:.*}", this::decryptSecret)
+ .get("/sign", this::sign)
+ .get("/hmac", this::hmac)
+ .get("/verify/sign/{text:.*}", this::verify)
+ .get("/verify/hmac/{text:.*}", this::verifyHmac)
+ .get("/disable", this::disableEngine);
+ }
+
+ private void enableEngine(ServerRequest req, ServerResponse res) {
+ sys.enableEngine(TransitSecrets.ENGINE);
+ res.send("Transit Secret engine enabled");
+ }
+
+ private void disableEngine(ServerRequest req, ServerResponse res) {
+ sys.disableEngine(TransitSecrets.ENGINE);
+ res.send("Transit Secret engine disabled");
+ }
+
+ private void createKeys(ServerRequest req, ServerResponse res) {
+ CreateKey.Request request = CreateKey.Request.builder()
+ .name(ENCRYPTION_KEY);
+
+ secrets.createKey(request);
+ secrets.createKey(CreateKey.Request.builder()
+ .name(SIGNATURE_KEY)
+ .type("rsa-2048"));
+
+ res.send("Created keys");
+ }
+
+ private void deleteKeys(ServerRequest req, ServerResponse res) {
+ secrets.updateKeyConfig(UpdateKeyConfig.Request.builder()
+ .name(ENCRYPTION_KEY)
+ .allowDeletion(true));
+ System.out.println("Updated key config");
+
+ secrets.deleteKey(DeleteKey.Request.create(ENCRYPTION_KEY));
+
+ res.send("Deleted key.");
+ }
+
+ private void decryptSecret(ServerRequest req, ServerResponse res) {
+ String encrypted = req.path().pathParameters().get("text");
+
+ Decrypt.Response decryptResponse = secrets.decrypt(Decrypt.Request.builder()
+ .encryptionKeyName(ENCRYPTION_KEY)
+ .cipherText(encrypted));
+
+ res.send(String.valueOf(decryptResponse.decrypted().toDecodedString()));
+ }
+
+ private void encryptSecret(ServerRequest req, ServerResponse res) {
+ String secret = req.path().pathParameters().get("text");
+
+ Encrypt.Response encryptResponse = secrets.encrypt(Encrypt.Request.builder()
+ .encryptionKeyName(ENCRYPTION_KEY)
+ .data(Base64Value.create(secret)));
+
+ res.send(encryptResponse.encrypted().cipherText());
+ }
+
+ private void hmac(ServerRequest req, ServerResponse res) {
+ Hmac.Response hmacResponse = secrets.hmac(Hmac.Request.builder()
+ .hmacKeyName(ENCRYPTION_KEY)
+ .data(SECRET_STRING));
+
+ res.send(hmacResponse.hmac());
+ }
+
+ private void sign(ServerRequest req, ServerResponse res) {
+ Sign.Response signResponse = secrets.sign(Sign.Request.builder()
+ .signatureKeyName(SIGNATURE_KEY)
+ .data(SECRET_STRING));
+
+ res.send(signResponse.signature());
+ }
+
+ private void verifyHmac(ServerRequest req, ServerResponse res) {
+ String hmac = req.path().pathParameters().get("text");
+
+ Verify.Response verifyResponse = secrets.verify(Verify.Request.builder()
+ .digestKeyName(ENCRYPTION_KEY)
+ .data(SECRET_STRING)
+ .hmac(hmac));
+
+ res.send("Valid: " + verifyResponse.isValid());
+ }
+
+ private void verify(ServerRequest req, ServerResponse res) {
+ String signature = req.path().pathParameters().get("text");
+
+ Verify.Response verifyResponse = secrets.verify(Verify.Request.builder()
+ .digestKeyName(SIGNATURE_KEY)
+ .data(SECRET_STRING)
+ .signature(signature));
+
+ res.send("Valid: " + verifyResponse.isValid());
+ }
+
+ private void batch(ServerRequest req, ServerResponse res) {
+ String[] data = new String[]{"one", "two", "three", "four"};
+ EncryptBatch.Request request = EncryptBatch.Request.builder()
+ .encryptionKeyName(ENCRYPTION_KEY);
+ DecryptBatch.Request decryptRequest = DecryptBatch.Request.builder()
+ .encryptionKeyName(ENCRYPTION_KEY);
+
+ for (String item : data) {
+ request.addEntry(EncryptBatch.BatchEntry.create(Base64Value.create(item)));
+ }
+ List batchResult = secrets.encrypt(request).batchResult();
+ for (Encrypt.Encrypted encrypted : batchResult) {
+ System.out.println("Encrypted: " + encrypted.cipherText());
+ decryptRequest.addEntry(DecryptBatch.BatchEntry.create(encrypted.cipherText()));
+ }
+
+ List base64Values = secrets.decrypt(decryptRequest).batchResult();
+ for (int i = 0; i < data.length; i++) {
+ String decryptedValue = base64Values.get(i).toDecodedString();
+ if (!data[i].equals(decryptedValue)) {
+ res.send("Data at index " + i + " is invalid. Decrypted " + decryptedValue
+ + ", expected: " + data[i]);
+ return;
+ }
+ }
+ res.send("Batch encryption/decryption completed");
+ }
+}
diff --git a/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/VaultMain.java b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/VaultMain.java
new file mode 100644
index 000000000..b336359e1
--- /dev/null
+++ b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/VaultMain.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.vault.hcp;
+
+import java.time.Duration;
+
+import io.helidon.config.Config;
+import io.helidon.integrations.vault.Vault;
+import io.helidon.integrations.vault.secrets.cubbyhole.CubbyholeSecrets;
+import io.helidon.integrations.vault.secrets.kv1.Kv1Secrets;
+import io.helidon.integrations.vault.secrets.kv2.Kv2Secrets;
+import io.helidon.integrations.vault.secrets.transit.TransitSecrets;
+import io.helidon.integrations.vault.sys.Sys;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+
+import static io.helidon.config.ConfigSources.classpath;
+import static io.helidon.config.ConfigSources.file;
+
+/**
+ * Main class of example.
+ */
+public final class VaultMain {
+ private VaultMain() {
+ }
+
+ /**
+ * Main method of example.
+ *
+ * @param args ignored
+ */
+ public static void main(String[] args) {
+ LogConfig.configureRuntime();
+
+ // as I cannot share my secret configuration, let's combine the configuration
+ // from my home directory with the one compiled into the jar
+ // when running this example, you can either update the application.yaml in resources directory
+ // or use the same approach
+ Config config = buildConfig();
+
+ // we have three configurations available
+ // 1. Token based authentication
+ Vault tokenVault = Vault.builder()
+ .config(config.get("vault.token"))
+ .updateWebClient(it -> it.connectTimeout(Duration.ofSeconds(5))
+ .readTimeout(Duration.ofSeconds(5)))
+ .build();
+
+ // 2. App role based authentication - must be created after we obtain the role id an token
+ // 3. Kubernetes (k8s) based authentication (requires to run on k8s) - must be created after we create
+ // the authentication method
+
+ // the tokenVault is using the root token and can be used to enable engines and
+ // other authentication mechanisms
+
+ System.out.println(new K8sExample(tokenVault, config.get("vault.k8s")).run());
+ System.out.println(new AppRoleExample(tokenVault, config.get("vault.approle")).run());
+
+ /*
+ We do not need to block here for our examples, as the server started below will keep the process running
+ */
+
+ Sys sys = tokenVault.sys(Sys.API);
+ // we use await for webserver, as we do not care if we block the main thread - it is not used
+ // for anything
+ WebServer webServer = WebServer.builder()
+ .config(config.get("server"))
+ .routing(routing -> routing
+ .register("/cubbyhole", new CubbyholeService(sys, tokenVault.secrets(CubbyholeSecrets.ENGINE)))
+ .register("/kv1", new Kv1Service(sys, tokenVault.secrets(Kv1Secrets.ENGINE)))
+ .register("/kv2", new Kv2Service(sys, tokenVault.secrets(Kv2Secrets.ENGINE)))
+ .register("/transit", new TransitService(sys, tokenVault.secrets(TransitSecrets.ENGINE))))
+ .build()
+ .start();
+
+ String baseAddress = "http://localhost:" + webServer.port() + "/";
+ System.out.println("Server started on " + baseAddress);
+ System.out.println();
+ System.out.println("Key/Value Version 1 Secrets Engine");
+ System.out.println("\t" + baseAddress + "kv1/enable");
+ System.out.println("\t" + baseAddress + "kv1/create");
+ System.out.println("\t" + baseAddress + "kv1/secrets/first/secret");
+ System.out.println("\tcurl -i -X DELETE " + baseAddress + "kv1/secrets/first/secret");
+ System.out.println("\t" + baseAddress + "kv1/disable");
+ System.out.println();
+ System.out.println("Key/Value Version 2 Secrets Engine");
+ System.out.println("\t" + baseAddress + "kv2/create");
+ System.out.println("\t" + baseAddress + "kv2/secrets/first/secret");
+ System.out.println("\tcurl -i -X DELETE " + baseAddress + "kv2/secrets/first/secret");
+ System.out.println();
+ System.out.println("Transit Secrets Engine");
+ System.out.println("\t" + baseAddress + "transit/enable");
+ System.out.println("\t" + baseAddress + "transit/keys");
+ System.out.println("\t" + baseAddress + "transit/encrypt/secret_text");
+ System.out.println("\t" + baseAddress + "transit/decrypt/cipher_text");
+ System.out.println("\t" + baseAddress + "transit/sign");
+ System.out.println("\t" + baseAddress + "transit/verify/sign/signature_text");
+ System.out.println("\t" + baseAddress + "transit/hmac");
+ System.out.println("\t" + baseAddress + "transit/verify/hmac/hmac_text");
+ System.out.println("\tcurl -i -X DELETE " + baseAddress + "transit/keys");
+ System.out.println("\t" + baseAddress + "transit/disable");
+ }
+
+ private static Config buildConfig() {
+ return Config.builder()
+ .sources(
+ // you can use this file to override the defaults that are built-in
+ file(System.getProperty("user.home") + "/helidon/conf/examples.yaml").optional(),
+ // in jar file (see src/main/resources/application.yaml)
+ classpath("application.yaml"))
+ .build();
+ }
+}
diff --git a/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/VaultPolicy.java b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/VaultPolicy.java
new file mode 100644
index 000000000..21fd349ed
--- /dev/null
+++ b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/VaultPolicy.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.integrations.vault.hcp;
+
+final class VaultPolicy {
+ private VaultPolicy() {
+ }
+ static final String POLICY = "# Enable and manage authentication methods\n"
+ + "path \"auth/*\"\n"
+ + "{\n"
+ + " capabilities = [\"create\", \"update\", \"delete\", \"sudo\"]\n"
+ + "}\n"
+ + "\n"
+ + "# Create, update, and delete auth methods\n"
+ + "path \"sys/auth/*\"\n"
+ + "{\n"
+ + " capabilities = [\"create\", \"update\", \"delete\", \"sudo\"]\n"
+ + "}\n"
+ + "\n"
+ + "# List auth methods\n"
+ + "path \"sys/auth\"\n"
+ + "{\n"
+ + " capabilities = [\"read\"]\n"
+ + "}\n"
+ + "\n"
+ + "# Enable and manage the key/value secrets engine at `secret/` path\n"
+ + "\n"
+ + "# List, create, update, and delete key/value secrets\n"
+ + "path \"secret/*\"\n"
+ + "{\n"
+ + " capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\", \"sudo\"]\n"
+ + "}\n"
+ + "\n"
+ + "path \"kv1/*\"\n"
+ + "{\n"
+ + " capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\", \"sudo\"]\n"
+ + "}\n"
+ + "\n"
+ + "path \"cubbyhole/*\"\n"
+ + "{\n"
+ + " capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\", \"sudo\"]\n"
+ + "}\n"
+ + "\n"
+ + "path \"database/*\"\n"
+ + "{\n"
+ + " capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\", \"sudo\"]\n"
+ + "}\n"
+ + "\n"
+ + "path \"kv/*\"\n"
+ + "{\n"
+ + " capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\", \"sudo\"]\n"
+ + "}\n"
+ + "\n"
+ + "# Manage secrets engines\n"
+ + "path \"sys/mounts/*\"\n"
+ + "{\n"
+ + " capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\", \"sudo\"]\n"
+ + "}\n"
+ + "\n"
+ + "# List existing secrets engines.\n"
+ + "path \"sys/mounts\"\n"
+ + "{\n"
+ + " capabilities = [\"read\"]\n"
+ + "}\n";
+}
diff --git a/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/package-info.java b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/package-info.java
new file mode 100644
index 000000000..6d423ad33
--- /dev/null
+++ b/examples/integrations/vault/hcp/src/main/java/io/helidon/examples/integrations/vault/hcp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example of Helidon integration with Hashicorp Vault.
+ */
+package io.helidon.examples.integrations.vault.hcp;
diff --git a/examples/integrations/vault/hcp/src/main/resources/application.yaml b/examples/integrations/vault/hcp/src/main/resources/application.yaml
new file mode 100644
index 000000000..718109f31
--- /dev/null
+++ b/examples/integrations/vault/hcp/src/main/resources/application.yaml
@@ -0,0 +1,59 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server.port: 8080
+
+vault:
+ properties:
+ address: "http://localhost:8200"
+ k8s:
+ address: "${vault.properties.address}"
+ # please change this to your k8s cluster address
+ # cluster-address: "https://kubernetes.docker.internal:6443"
+ cluster-address: "https://10.96.0.1"
+ auth:
+ k8s:
+ enabled: true
+ # this role is created in the code, must be the same value
+ token-role: my-role
+ service-account-token: "${vault.properties.k8s.service-account-token}"
+ app-role:
+ enabled: false
+ token:
+ enabled: false
+ token:
+ token: "myroot"
+ address: "${vault.properties.address}"
+ auth:
+ k8s:
+ enabled: false
+ app-role:
+ enabled: false
+ token:
+ enabled: true
+ approle:
+ address: "${vault.properties.address}"
+ auth:
+ k8s:
+ enabled: false
+ app-role:
+ # this is not needed, as we use a builder,
+ # it is here to show how this could be used to define a
+ # custom path for vault authentication (same can be done for k8s)
+ path: "customapprole"
+ enabled: true
+ token:
+ enabled: false
\ No newline at end of file
diff --git a/examples/integrations/vault/hcp/src/main/resources/logging.properties b/examples/integrations/vault/hcp/src/main/resources/logging.properties
new file mode 100644
index 000000000..8e28f63f9
--- /dev/null
+++ b/examples/integrations/vault/hcp/src/main/resources/logging.properties
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+io.helidon.level=INFO
+io.helidon.integrations.level=INFO
diff --git a/examples/integrations/vault/pom.xml b/examples/integrations/vault/pom.xml
new file mode 100644
index 000000000..d6135601b
--- /dev/null
+++ b/examples/integrations/vault/pom.xml
@@ -0,0 +1,39 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples.integrations
+ helidon-examples-integrations-project
+ 1.0.0-SNAPSHOT
+
+ io.helidon.examples.integrations.vault
+ helidon-examples-integrations-vault-project
+ 1.0.0-SNAPSHOT
+ pom
+ Helidon Examples Integration Vault
+ Examples of integration with Vault.
+
+
+ hcp
+ hcp-cdi
+
+
diff --git a/examples/jbatch/README.md b/examples/jbatch/README.md
new file mode 100644
index 000000000..551fcdef8
--- /dev/null
+++ b/examples/jbatch/README.md
@@ -0,0 +1,16 @@
+# Helidon + jBatch
+
+Minimal Helidon MP + jBatch PoC.
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-jbatch.jar
+```
+
+## Exercise the application
+
+```shell
+curl -X GET http://localhost:8080/batch
+```
\ No newline at end of file
diff --git a/examples/jbatch/pom.xml b/examples/jbatch/pom.xml
new file mode 100644
index 000000000..85a3fa68d
--- /dev/null
+++ b/examples/jbatch/pom.xml
@@ -0,0 +1,133 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.jbatch
+ helidon-examples-jbatch
+ 1.0.0-SNAPSHOT
+ Helidon Examples JBatch
+
+
+ 2.1.0
+ 2.1.0
+ 10.14.2.0
+ 3.0.1
+
+
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile-core
+
+
+ io.helidon.logging
+ helidon-logging-jul
+ runtime
+
+
+ jakarta.json
+ jakarta.json-api
+
+
+ jakarta.transaction
+ jakarta.transaction-api
+
+
+ jakarta.batch
+ jakarta.batch-api
+ ${version.lib.jbatch-api}
+
+
+ com.ibm.jbatch
+ com.ibm.jbatch.spi
+ ${version.lib.jbatch.container}
+
+
+ org.glassfish.jaxb
+ jaxb-runtime
+ ${version.lib.jaxb-api}
+
+
+
+
+ io.smallrye
+ jandex
+ runtime
+ true
+
+
+ com.ibm.jbatch
+ com.ibm.jbatch.container
+ ${version.lib.jbatch.container}
+ runtime
+
+
+ org.apache.derby
+ derby
+ ${version.lib.derby}
+ runtime
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ io.helidon.microprofile.testing
+ helidon-microprofile-testing-junit5
+ test
+
+
+ org.hamcrest
+ hamcrest-core
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
diff --git a/examples/jbatch/src/main/java/io/helidon/examples/jbatch/BatchResource.java b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/BatchResource.java
new file mode 100644
index 000000000..df5674237
--- /dev/null
+++ b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/BatchResource.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.jbatch;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+import com.ibm.jbatch.spi.BatchSPIManager;
+import jakarta.batch.operations.JobOperator;
+import jakarta.batch.runtime.JobExecution;
+import jakarta.batch.runtime.StepExecution;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.json.Json;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+import static jakarta.batch.runtime.BatchRuntime.getJobOperator;
+
+
+/**
+ * Trigger a batch process using resource.
+ */
+@Path("/batch")
+@ApplicationScoped
+public class BatchResource {
+ private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
+
+ private JobOperator jobOperator;
+
+ /**
+ * Run a JBatch process when endpoint called.
+ * @return JsonObject with the result.
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public JsonObject executeBatch() {
+
+ BatchSPIManager batchSPIManager = BatchSPIManager.getInstance();
+ batchSPIManager.registerPlatformMode(BatchSPIManager.PlatformMode.SE);
+ batchSPIManager.registerExecutorServiceProvider(new HelidonExecutorServiceProvider());
+
+ jobOperator = getJobOperator();
+ Long executionId = jobOperator.start("myJob", new Properties());
+
+ return JSON.createObjectBuilder()
+ .add("Started a job with Execution ID: ", executionId)
+ .build();
+ }
+
+ /**
+ * Check the job status.
+ * @param executionId the job ID.
+ * @return JsonObject with status.
+ */
+ @GET
+ @Path("/status/{execution-id}")
+ public JsonObject status(@PathParam("execution-id") Long executionId){
+ JobExecution jobExecution = jobOperator.getJobExecution(executionId);
+
+ List stepExecutions = jobOperator.getStepExecutions(executionId);
+ List executedSteps = new ArrayList<>();
+ for (StepExecution stepExecution : stepExecutions) {
+ executedSteps.add(stepExecution.getStepName());
+ }
+
+ return JSON.createObjectBuilder()
+ .add("Steps executed", Arrays.toString(executedSteps.toArray()))
+ .add("Status", jobExecution.getBatchStatus().toString())
+ .build();
+ }
+}
diff --git a/examples/jbatch/src/main/java/io/helidon/examples/jbatch/HelidonExecutorServiceProvider.java b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/HelidonExecutorServiceProvider.java
new file mode 100644
index 000000000..f7fc5949b
--- /dev/null
+++ b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/HelidonExecutorServiceProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.jbatch;
+
+import java.util.concurrent.ExecutorService;
+
+import io.helidon.common.configurable.ThreadPoolSupplier;
+
+import com.ibm.jbatch.spi.ExecutorServiceProvider;
+
+
+/**
+ * Executor service for batch processing.
+ */
+public class HelidonExecutorServiceProvider implements ExecutorServiceProvider {
+ @Override
+ public ExecutorService getExecutorService() {
+ return ThreadPoolSupplier.builder().corePoolSize(2).build().get();
+ }
+}
diff --git a/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyBatchlet.java b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyBatchlet.java
new file mode 100644
index 000000000..fab5f0fd5
--- /dev/null
+++ b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyBatchlet.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.jbatch.jobs;
+
+import jakarta.batch.api.AbstractBatchlet;
+
+/**
+ * Batchlet example.
+ */
+public class MyBatchlet extends AbstractBatchlet {
+
+ /**
+ * Run inside a batchlet.
+ *
+ * @return String with status.
+ */
+ @Override
+ public String process() {
+ System.out.println("Running inside a batchlet");
+ return "COMPLETED";
+ }
+
+}
diff --git a/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyInputRecord.java b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyInputRecord.java
new file mode 100644
index 000000000..b9f73a438
--- /dev/null
+++ b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyInputRecord.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.jbatch.jobs;
+
+/**
+ * Example of an Input Record.
+ */
+public class MyInputRecord {
+ private int id;
+
+ /**
+ * Constructor for Input Record.
+ * @param id
+ */
+ public MyInputRecord(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return "MyInputRecord: " + id;
+ }
+}
diff --git a/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyItemProcessor.java b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyItemProcessor.java
new file mode 100644
index 000000000..f39c15868
--- /dev/null
+++ b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyItemProcessor.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.jbatch.jobs;
+
+import jakarta.batch.api.chunk.ItemProcessor;
+
+/**
+ * Example Item Processor.
+ */
+public class MyItemProcessor implements ItemProcessor {
+
+ @Override
+ public MyOutputRecord processItem(Object t) {
+ System.out.println("processItem: " + t);
+
+ return (((MyInputRecord) t).getId() % 2 == 0) ? null : new MyOutputRecord(((MyInputRecord) t).getId() * 2);
+ }
+}
diff --git a/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyItemReader.java b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyItemReader.java
new file mode 100644
index 000000000..e6d12ea75
--- /dev/null
+++ b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyItemReader.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.jbatch.jobs;
+
+import java.util.StringTokenizer;
+
+import jakarta.batch.api.chunk.AbstractItemReader;
+
+
+/**
+ * Example Item Reader.
+ */
+public class MyItemReader extends AbstractItemReader {
+
+ private final StringTokenizer tokens;
+
+ /**
+ * Constructor for Item Reader.
+ */
+ public MyItemReader() {
+ tokens = new StringTokenizer("1,2,3,4,5,6,7,8,9,10", ",");
+ }
+
+ /**
+ * Perform read Item.
+ * @return Stage result.
+ */
+ @Override
+ public MyInputRecord readItem() {
+ if (tokens.hasMoreTokens()) {
+ return new MyInputRecord(Integer.valueOf(tokens.nextToken()));
+ }
+ return null;
+ }
+}
diff --git a/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyItemWriter.java b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyItemWriter.java
new file mode 100644
index 000000000..66572dd30
--- /dev/null
+++ b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyItemWriter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.jbatch.jobs;
+
+import java.util.List;
+
+import jakarta.batch.api.chunk.AbstractItemWriter;
+
+
+/**
+ * Example Item Writer.
+ */
+public class MyItemWriter extends AbstractItemWriter {
+
+ @Override
+ public void writeItems(List list) {
+ System.out.println("writeItems: " + list);
+ }
+}
diff --git a/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyOutputRecord.java b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyOutputRecord.java
new file mode 100644
index 000000000..24e0b11a6
--- /dev/null
+++ b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/MyOutputRecord.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.jbatch.jobs;
+
+/**
+ * Example Output Processor.
+ */
+public class MyOutputRecord {
+
+ private int id;
+
+ /**
+ * Constructor for Output Record.
+ * @param id
+ */
+ public MyOutputRecord(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return "MyOutputRecord: " + id;
+ }
+}
diff --git a/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/package-info.java b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/package-info.java
new file mode 100644
index 000000000..fef1a02b6
--- /dev/null
+++ b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/jobs/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * JBatch specific classes.
+ */
+package io.helidon.examples.jbatch.jobs;
diff --git a/examples/jbatch/src/main/java/io/helidon/examples/jbatch/package-info.java b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/package-info.java
new file mode 100644
index 000000000..f49bf46eb
--- /dev/null
+++ b/examples/jbatch/src/main/java/io/helidon/examples/jbatch/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example application showing support for JBatch and HelidonMP.
+ */
+package io.helidon.examples.jbatch;
diff --git a/examples/jbatch/src/main/resources/META-INF/batch-jobs/myJob.xml b/examples/jbatch/src/main/resources/META-INF/batch-jobs/myJob.xml
new file mode 100644
index 000000000..754a82135
--- /dev/null
+++ b/examples/jbatch/src/main/resources/META-INF/batch-jobs/myJob.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/jbatch/src/main/resources/META-INF/beans.xml b/examples/jbatch/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..292cd41a4
--- /dev/null
+++ b/examples/jbatch/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/jbatch/src/main/resources/META-INF/helidon/serial-config.properties b/examples/jbatch/src/main/resources/META-INF/helidon/serial-config.properties
new file mode 100644
index 000000000..88030d7f9
--- /dev/null
+++ b/examples/jbatch/src/main/resources/META-INF/helidon/serial-config.properties
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# The JBatch uses Serialization a lot, and these are all required
+pattern=com.ibm.jbatch.**;jakarta.batch.runtime.BatchStatus;java.lang.Enum;\
+ java.util.Properties;java.util.Hashtable;java.util.Map$Entry
diff --git a/examples/jbatch/src/main/resources/META-INF/microprofile-config.properties b/examples/jbatch/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 000000000..bdaf6133e
--- /dev/null
+++ b/examples/jbatch/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,22 @@
+#
+# Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Microprofile server properties
+server.port=8080
+server.host=0.0.0.0
+
+# Change the following to true to enable the optional MicroProfile Metrics REST.request metrics
+metrics.rest-request.enabled=false
diff --git a/examples/jbatch/src/main/resources/logging.properties b/examples/jbatch/src/main/resources/logging.properties
new file mode 100644
index 000000000..ef323f89b
--- /dev/null
+++ b/examples/jbatch/src/main/resources/logging.properties
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+# Quiet Weld
+org.jboss.level=WARNING
diff --git a/examples/jbatch/src/test/java/io/helidon/examples/jbatch/TestJBatchEndpoint.java b/examples/jbatch/src/test/java/io/helidon/examples/jbatch/TestJBatchEndpoint.java
new file mode 100644
index 000000000..dfdcb1a39
--- /dev/null
+++ b/examples/jbatch/src/test/java/io/helidon/examples/jbatch/TestJBatchEndpoint.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.jbatch;
+
+import java.util.Collections;
+
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+
+import jakarta.inject.Inject;
+import jakarta.json.Json;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.MediaType;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@HelidonTest
+public class TestJBatchEndpoint {
+
+ private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
+
+ @Inject
+ private WebTarget webTarget;
+
+ @Test
+ public void runJob() throws InterruptedException {
+
+ JsonObject expectedJson = JSON.createObjectBuilder()
+ .add("Steps executed", "[step1, step2]")
+ .add("Status", "COMPLETED")
+ .build();
+
+ //Start the job
+ JsonObject jsonObject = webTarget
+ .path("/batch")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .get(JsonObject.class);
+
+ Integer responseJobId = jsonObject.getInt("Started a job with Execution ID: ");
+ assertThat(responseJobId, is(notNullValue()));
+ JsonObject result = null;
+ for (int i = 1; i < 10; i++) {
+ //Wait a bit for it to complete
+ Thread.sleep(i*1000);
+
+ //Examine the results
+ result = webTarget
+ .path("batch/status/" + responseJobId)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .get(JsonObject.class);
+
+ if (result.equals(expectedJson)){
+ break;
+ }
+
+ }
+
+ assertThat(result, equalTo(expectedJson));
+ }
+}
diff --git a/examples/k8s/zipkin.yaml b/examples/k8s/zipkin.yaml
new file mode 100644
index 000000000..5b632ff78
--- /dev/null
+++ b/examples/k8s/zipkin.yaml
@@ -0,0 +1,73 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: zipkin
+ labels:
+ app: zipkin
+spec:
+ selector:
+ matchLabels:
+ app: zipkin
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ app: zipkin
+ spec:
+ containers:
+ - name: zipkin
+ image: openzipkin/zipkin:2
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 9411
+---
+
+apiVersion: v1
+kind: Service
+metadata:
+ name: zipkin
+ labels:
+ app: zipkin
+spec:
+ type: ClusterIP
+ selector:
+ app: zipkin
+ ports:
+ - port: 9411
+ targetPort: 9411
+ name: http
+
+---
+
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: zipkin-ingress
+spec:
+ rules:
+ - host: localhost
+ http:
+ paths:
+ - path: /zipkin
+ pathType: Prefix
+ backend:
+ service:
+ name: zipkin
+ port:
+ number: 9411
diff --git a/examples/logging/jul/README.md b/examples/logging/jul/README.md
new file mode 100644
index 000000000..44a10a6a2
--- /dev/null
+++ b/examples/logging/jul/README.md
@@ -0,0 +1,52 @@
+JUL Example
+---
+
+This example shows how to use Java Util Logging with MDC
+ using Helidon API.
+
+The example can be built using GraalVM native image as well.
+
+# Running as jar
+
+Build this application:
+```shell
+mvn clean package
+```
+
+Run from command line:
+```shell
+java -jar target/helidon-examples-logging-jul.jar
+```
+
+Expected output should be similar to the following:
+```text
+2020.11.19 15:37:28 INFO io.helidon.logging.jul.JulProvider Thread[#1,main,5,main]: Logging at initialization configured using classpath: /logging.properties ""
+2020.11.19 15:37:28 INFO io.helidon.examples.logging.jul.Main Thread[main,5,main]: Starting up "startup"
+2020.11.19 15:37:28 INFO io.helidon.examples.logging.jul.Main Thread[pool-1-thread-1,5,main]: Running on another thread "propagated"
+2020.11.19 15:37:28 INFO io.helidon.common.features.HelidonFeatures Thread[#23,features-thread,5,main]: Helidon 4.0.0-SNAPSHOT features: [Config, Encoding, Media, WebServer] ""
+2020.11.19 15:37:28 INFO io.helidon.webserver.LoomServer Thread[#1,main,5,main]: Started all channels in 46 milliseconds. 577 milliseconds since JVM startup. Java 20.0.1+9-29 "propagated"
+```
+
+# Running as native image
+You must use GraalVM with native image installed as your JDK,
+or you can specify an environment variable `GRAALVM_HOME` that points
+to such an installation.
+
+Build this application:
+```shell
+mvn clean package -Pnative-image
+```
+
+Run from command line:
+```shell
+./target/helidon-examples-logging-jul
+```
+
+Expected output should be similar to the following:
+```text
+2020.11.19 15:38:14 INFO io.helidon.logging.common.LogConfig Thread[main,5,main]: Logging at runtime configured using classpath: /logging.properties ""
+2020.11.19 15:38:14 INFO io.helidon.examples.logging.jul.Main Thread[main,5,main]: Starting up "startup"
+2020.11.19 15:38:14 INFO io.helidon.examples.logging.jul.Main Thread[pool-1-thread-1,5,main]: Running on another thread "propagated"
+2020.11.19 15:38:14 INFO io.helidon.common.features.HelidonFeatures Thread[features-thread,5,main]: Helidon SE 2.2.0 features: [Config, WebServer] ""
+2020.11.19 15:38:14 INFO io.helidon.reactive.webserver.NettyWebServer Thread[nioEventLoopGroup-2-1,10,main]: Channel '@default' started: [id: 0x2b929906, L:/0:0:0:0:0:0:0:0:8080] ""
+```
\ No newline at end of file
diff --git a/examples/logging/jul/pom.xml b/examples/logging/jul/pom.xml
new file mode 100644
index 000000000..0e348e947
--- /dev/null
+++ b/examples/logging/jul/pom.xml
@@ -0,0 +1,62 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.logging
+ helidon-examples-logging-jul
+ 1.0.0-SNAPSHOT
+ Helidon Examples Logging Java Util Logging
+
+
+ Example of logging and MDC using JUL
+
+
+
+ io.helidon.examples.logging.jul.Main
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/logging/jul/src/main/java/io/helidon/examples/logging/jul/Main.java b/examples/logging/jul/src/main/java/io/helidon/examples/logging/jul/Main.java
new file mode 100644
index 000000000..c30a03f15
--- /dev/null
+++ b/examples/logging/jul/src/main/java/io/helidon/examples/logging/jul/Main.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.logging.jul;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.logging.Logger;
+
+import io.helidon.common.context.Context;
+import io.helidon.common.context.Contexts;
+import io.helidon.logging.common.HelidonMdc;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.http.HttpRouting;
+
+/**
+ * Main class of the example, runnable from command line.
+ */
+public final class Main {
+ private static final Logger LOGGER = Logger.getLogger(Main.class.getName());
+
+ private Main() {
+ }
+
+ /**
+ * Starts the example.
+ *
+ * @param args not used
+ */
+ public static void main(String[] args) {
+ LogConfig.configureRuntime();
+
+ // the Helidon context is used to propagate MDC across threads
+ // if running within Helidon WebServer, you do not need to runInContext, as that is already
+ // done by the webserver
+ Contexts.runInContext(Context.create(), Main::logging);
+
+ WebServer server = WebServer.builder()
+ .port(8080)
+ .routing(Main::routing)
+ .build()
+ .start();
+ }
+
+ private static void routing(HttpRouting.Builder routing) {
+ routing.get("/", (req, res) -> {
+ HelidonMdc.set("name", String.valueOf(req.id()));
+ LOGGER.info("Running in webserver, id:");
+ res.send("Hello");
+ });
+ }
+
+ private static void logging() {
+ HelidonMdc.set("name", "startup");
+ LOGGER.info("Starting up");
+
+ // now let's see propagation across executor service boundary
+ HelidonMdc.set("name", "propagated");
+ // wrap executor so it supports Helidon context, this is done for all built-in executors in Helidon
+ ExecutorService es = Contexts.wrap(Executors.newSingleThreadExecutor());
+
+ Future> submit = es.submit(() -> {
+ LOGGER.info("Running on another thread");
+ });
+ try {
+ submit.get();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ es.shutdown();
+ }
+}
diff --git a/examples/logging/jul/src/main/java/io/helidon/examples/logging/jul/package-info.java b/examples/logging/jul/src/main/java/io/helidon/examples/logging/jul/package-info.java
new file mode 100644
index 000000000..a7fe560e7
--- /dev/null
+++ b/examples/logging/jul/src/main/java/io/helidon/examples/logging/jul/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 util logging example with Mapped diagnostics context in Helidon.
+ */
+package io.helidon.examples.logging.jul;
diff --git a/examples/logging/jul/src/main/resources/logging.properties b/examples/logging/jul/src/main/resources/logging.properties
new file mode 100644
index 000000000..90de19dd6
--- /dev/null
+++ b/examples/logging/jul/src/main/resources/logging.properties
@@ -0,0 +1,25 @@
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# !thread! is replaced by Helidon with the thread name
+# any %X{...} is replaced by a value from MDC
+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 "%X{name}"%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
diff --git a/examples/logging/log4j/README.md b/examples/logging/log4j/README.md
new file mode 100644
index 000000000..bf7f61892
--- /dev/null
+++ b/examples/logging/log4j/README.md
@@ -0,0 +1,55 @@
+Log4j Example
+---
+
+This example shows how to use log4j with MDC (`ThreadContext`)
+ using Helidon API.
+
+The example moves all Java Util Logging to log4j.
+
+The example can be built using GraalVM native image as well.
+
+# Running as jar
+
+Build this application:
+```shell
+mvn clean package
+```
+
+Run from command line:
+```shell
+java -jar target/helidon-examples-logging-log4j.jar
+```
+
+Expected output should be similar to the following:
+```text
+15:44:48.596 INFO [main] io.helidon.examples.logging.log4j.Main - Starting up "startup"
+15:44:48.598 INFO [main] io.helidon.examples.logging.log4j.Main - Using System logger "startup"
+15:44:48.600 INFO [pool-2-thread-1] io.helidon.examples.logging.log4j.Main - Running on another thread "propagated"
+15:44:48.704 INFO [features-thread] io.helidon.common.features.HelidonFeatures - Helidon 4.0.0-SNAPSHOT features: [Config, Encoding, Media, WebServer] ""
+15:44:48.801 INFO [main] io.helidon.webserver.LoomServer - Started all channels in 12 milliseconds. 746 milliseconds since JVM startup. Java 20.0.1+9-29 "propagated"
+```
+
+# Running as native image
+You must use GraalVM with native image installed as your JDK,
+or you can specify an environment variable `GRAALVM_HOME` that points
+to such an installation.
+
+Build this application:
+```shell
+mvn clean package -Pnative-image
+```
+
+Run from command line:
+```shell
+./target/helidon-examples-logging-log4j
+```
+
+*In native image, we can only replace loggers initialized after reconfiguration of logging system
+This unfortunately means that Helidon logging would not be available*
+
+Expected output should be similar to the following:
+```text
+15:47:53.033 INFO [main] io.helidon.examples.logging.log4j.Main - Starting up "startup"
+15:47:53.033 INFO [main] io.helidon.examples.logging.log4j.Main - Using JUL logger "startup"
+15:47:53.033 INFO [pool-2-thread-1] io.helidon.examples.logging.log4j.Main - Running on another thread "propagated"
+```
\ No newline at end of file
diff --git a/examples/logging/log4j/pom.xml b/examples/logging/log4j/pom.xml
new file mode 100644
index 000000000..776392a49
--- /dev/null
+++ b/examples/logging/log4j/pom.xml
@@ -0,0 +1,78 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.logging
+ helidon-examples-logging-log4j
+ 1.0.0-SNAPSHOT
+ Helidon Examples Logging Log4j
+
+
+ Example of logging and MDC using Log4j
+
+
+
+ io.helidon.examples.logging.log4j.Main
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.logging
+ helidon-logging-log4j
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.apache.logging.log4j
+ log4j-jul
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/logging/log4j/src/main/java/io/helidon/examples/logging/log4j/Main.java b/examples/logging/log4j/src/main/java/io/helidon/examples/logging/log4j/Main.java
new file mode 100644
index 000000000..4ab382076
--- /dev/null
+++ b/examples/logging/log4j/src/main/java/io/helidon/examples/logging/log4j/Main.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.logging.log4j;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import io.helidon.common.context.Context;
+import io.helidon.common.context.Contexts;
+import io.helidon.logging.common.HelidonMdc;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.http.HttpRouting;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.appender.ConsoleAppender;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
+
+/**
+ * Main class of the example, runnable from command line.
+ * There is a limitation of log4j in native image - we only have loggers that are
+ * initialized after we configure logging, which unfortunately excludes Helidon loggers.
+ * You would need to use JUL or slf4j to have Helidon logs combined with application logs.
+ */
+public final class Main {
+ private static System.Logger systemLogger;
+ private static Logger logger;
+
+ private Main() {
+ }
+
+ /**
+ * Starts the example.
+ *
+ * @param args not used
+ */
+ public static void main(String[] args) {
+ // file based logging configuration does not work
+ // with native image!
+ configureLog4j();
+ LogConfig.configureRuntime();
+ // get logger after configuration
+ logger = LogManager.getLogger(Main.class);
+ systemLogger = System.getLogger(Main.class.getName());
+
+ // the Helidon context is used to propagate MDC across threads
+ // if running within Helidon WebServer, you do not need to runInContext, as that is already
+ // done by the webserver
+ Contexts.runInContext(Context.create(), Main::logging);
+
+ WebServer server = WebServer.builder()
+ .routing(Main::routing)
+ .build()
+ .start();
+ }
+
+ private static void routing(HttpRouting.Builder routing) {
+ routing.get("/", (req, res) -> {
+ HelidonMdc.set("name", String.valueOf(req.id()));
+ logger.info("Running in webserver, id:");
+ res.send("Hello");
+ });
+ }
+
+ private static void logging() {
+ HelidonMdc.set("name", "startup");
+ logger.info("Starting up");
+ systemLogger.log(System.Logger.Level.INFO, "Using System logger");
+
+ // now let's see propagation across executor service boundary, we can also use Log4j's ThreadContext
+ ThreadContext.put("name", "propagated");
+ // wrap executor so it supports Helidon context, this is done for all built-in executors in Helidon
+ ExecutorService es = Contexts.wrap(Executors.newSingleThreadExecutor());
+
+ Future> submit = es.submit(Main::log);
+ try {
+ submit.get();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ es.shutdown();
+ }
+
+ private static void log() {
+ logger.info("Running on another thread");
+ }
+
+ private static void configureLog4j() {
+ // configure log4j
+ final var builder = ConfigurationBuilderFactory.newConfigurationBuilder();
+ builder.setConfigurationName("root");
+ builder.setStatusLevel(Level.INFO);
+ final var appenderComponentBuilder = builder.newAppender("Stdout", "CONSOLE")
+ .addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT);
+ appenderComponentBuilder.add(builder.newLayout("PatternLayout")
+ .addAttribute("pattern", "%d{HH:mm:ss.SSS} %-5level [%t] %logger{36} - %msg "
+ + "\"%X{name}\"%n"));
+ builder.add(appenderComponentBuilder);
+ builder.add(builder.newRootLogger(Level.INFO)
+ .add(builder.newAppenderRef("Stdout")));
+ Configurator.initialize(builder.build());
+ }
+}
diff --git a/examples/logging/log4j/src/main/java/io/helidon/examples/logging/log4j/package-info.java b/examples/logging/log4j/src/main/java/io/helidon/examples/logging/log4j/package-info.java
new file mode 100644
index 000000000..dfbef7be1
--- /dev/null
+++ b/examples/logging/log4j/src/main/java/io/helidon/examples/logging/log4j/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Log4j example with Mapped diagnostics context (ThreadContext) in Helidon.
+ */
+package io.helidon.examples.logging.log4j;
diff --git a/examples/logging/logback-aot/README.md b/examples/logging/logback-aot/README.md
new file mode 100644
index 000000000..1ba544c0b
--- /dev/null
+++ b/examples/logging/logback-aot/README.md
@@ -0,0 +1,60 @@
+Slf4j/Logback Example
+---
+
+This example shows how to use slf4j with MDC backed by Logback
+ using Helidon API.
+
+The example moves all Java Util Logging to slf4j and supports more advance configuration of logback.
+
+# AOT (native image)
+To support native image, we need to use a different logback configuration at build time and at runtime.
+To achieve this, we bundle `logback.xml` on classpath, and then have `logback-runtime.xml` with
+configuration that requires started threads (which is not supported at build time).
+
+The implementation will re-configure logback (see method `setupLogging` in `Main.java).
+
+To see that configuration works as expected at runtime, change the log level of our package to `debug`.
+Within 30 seconds the configuration should be reloaded, and next request will have two more debug messages.
+
+Expected output should be similar to the following (for both hotspot and native):
+```text
+15:40:44.240 [INFO ] [io.helidon.examples.logging.logback.aot.Main.logging:128] Starting up startup
+15:40:44.241 [INFO ] [o.slf4j.jdk.platform.logging.SLF4JPlatformLogger.performLog:151] Using System logger startup
+15:40:44.245 [INFO ] [io.helidon.examples.logging.logback.aot.Main.log:146] Running on another thread propagated
+15:40:44.395 [INFO ] [o.slf4j.jdk.platform.logging.SLF4JPlatformLogger.performLog:151] Helidon 4.0.0-SNAPSHOT features: [Config, Encoding, Media, WebServer]
+15:40:44.538 [INFO ] [o.slf4j.jdk.platform.logging.SLF4JPlatformLogger.performLog:151] Started all channels in 15 milliseconds. 647 milliseconds since JVM startup. Java 20.0.1+9-29 propagated
+```
+
+The output is also logged into `helidon.log`.
+
+# Running as jar
+
+Build this application:
+```shell
+mvn clean package
+```
+
+Run from command line:
+```shell
+java -jar target/helidon-examples-logging-slf4j-aot.jar
+```
+
+Execute endpoint:
+```shell
+curl -i http://localhost:8080
+```
+
+# Running as native image
+You must use GraalVM with native image installed as your JDK,
+or you can specify an environment variable `GRAALVM_HOME` that points
+to such an installation.
+
+Build this application:
+```shell script
+mvn clean package -Pnative-image
+```
+
+Run from command line:
+```shell
+./target/helidon-examples-logging-slf4j-aot
+```
diff --git a/examples/logging/logback-aot/logback-runtime.xml b/examples/logging/logback-aot/logback-runtime.xml
new file mode 100644
index 000000000..5d636ad19
--- /dev/null
+++ b/examples/logging/logback-aot/logback-runtime.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ helidon.log
+ true
+ false
+
+ helidon.%d{yyyy-MM-dd}.gz
+ 10
+ 5GB
+
+
+
+ ${defaultPattern}
+
+
+
+
+
+ 2048
+ 15000
+ true
+ true
+
+
+
+
+ ${defaultPattern}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/logging/logback-aot/pom.xml b/examples/logging/logback-aot/pom.xml
new file mode 100644
index 000000000..453d9397f
--- /dev/null
+++ b/examples/logging/logback-aot/pom.xml
@@ -0,0 +1,79 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.logging
+ helidon-examples-logging-slf4j-aot
+ 1.0.0-SNAPSHOT
+ Helidon Examples Logging Slf4j AOT
+
+
+ Example of logging and MDC using Slf4j ready for Ahead of time compilation
+ using GraalVM native image
+
+
+
+ io.helidon.examples.logging.logback.aot.Main
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.logging
+ helidon-logging-slf4j
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.slf4j
+ jul-to-slf4j
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/logging/logback-aot/src/main/java/io/helidon/examples/logging/logback/aot/Main.java b/examples/logging/logback-aot/src/main/java/io/helidon/examples/logging/logback/aot/Main.java
new file mode 100644
index 000000000..fb86d733c
--- /dev/null
+++ b/examples/logging/logback-aot/src/main/java/io/helidon/examples/logging/logback/aot/Main.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.logging.logback.aot;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import io.helidon.common.context.Context;
+import io.helidon.common.context.Contexts;
+import io.helidon.logging.common.HelidonMdc;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.http.HttpRouting;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.core.joran.spi.JoranException;
+import ch.qos.logback.core.util.StatusPrinter;
+import org.slf4j.ILoggerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+
+/**
+ * Main class of the example, runnable from command line.
+ */
+public final class Main {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
+ private static final System.Logger SYSTEM_LOGGER = System.getLogger(Main.class.getName());
+
+ private Main() {
+ }
+
+ /**
+ * Starts the example.
+ *
+ * @param args not used
+ */
+ public static void main(String[] args) {
+ // use slf4j for JUL as well
+ setupLogging();
+
+ // the Helidon context is used to propagate MDC across threads
+ // if running within Helidon WebServer, you do not need to runInContext, as that is already
+ // done by the webserver
+ Contexts.runInContext(Context.create(), Main::logging);
+
+ WebServer server = WebServer.builder()
+ .port(8080)
+ .routing(Main::routing)
+ .build()
+ .start();
+ }
+
+ private static void routing(HttpRouting.Builder routing) {
+ routing.get("/", (req, res) -> {
+ HelidonMdc.set("name", String.valueOf(req.id()));
+ LOGGER.debug("Debug message to show runtime reloading works");
+ LOGGER.info("Running in webserver, id:");
+ res.send("Hello");
+ LOGGER.debug("Response sent");
+ });
+ }
+
+ private static void setupLogging() {
+ String location = System.getProperty("logback.configurationFile");
+ location = (location == null) ? "logback-runtime.xml" : location;
+ // we cannot use anything that starts threads at build time, must re-configure here
+ resetLogging(location);
+ }
+
+ private static void resetLogging(String location) {
+ ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
+ if (loggerFactory instanceof LoggerContext) {
+ resetLogging(location, (LoggerContext) loggerFactory);
+ } else {
+ LOGGER.warn("Expecting a logback implementation, but got " + loggerFactory.getClass().getName());
+ }
+ }
+
+ private static void resetLogging(String location, LoggerContext loggerFactory) {
+ JoranConfigurator configurator = new JoranConfigurator();
+
+ configurator.setContext(loggerFactory);
+ loggerFactory.reset();
+
+ try {
+ configurator.doConfigure(location);
+
+ Logger instance = LoggerFactory.getLogger(Main.class);
+ instance.info("Runtime logging configured from file \"{}\".", location);
+ StatusPrinter.print(loggerFactory);
+ } catch (JoranException e) {
+ LOGGER.warn("Failed to reload logging from " + location, e);
+ e.printStackTrace();
+ }
+ }
+
+ private static void logging() {
+ HelidonMdc.set("name", "startup");
+ LOGGER.info("Starting up");
+ SYSTEM_LOGGER.log(System.Logger.Level.INFO, "Using System logger");
+
+ // now let's see propagation across executor service boundary, we can also use Log4j's ThreadContext
+ MDC.put("name", "propagated");
+ // wrap executor so it supports Helidon context, this is done for all built-in executors in Helidon
+ ExecutorService es = Contexts.wrap(Executors.newSingleThreadExecutor());
+
+ Future> submit = es.submit(Main::log);
+ try {
+ submit.get();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ es.shutdown();
+ }
+
+ private static void log() {
+ LOGGER.info("Running on another thread");
+ }
+}
diff --git a/examples/logging/logback-aot/src/main/java/io/helidon/examples/logging/logback/aot/package-info.java b/examples/logging/logback-aot/src/main/java/io/helidon/examples/logging/logback/aot/package-info.java
new file mode 100644
index 000000000..6e718eafd
--- /dev/null
+++ b/examples/logging/logback-aot/src/main/java/io/helidon/examples/logging/logback/aot/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Slf4j example with Mapped diagnostics context (MDC) in Helidon ready for
+ * complex runtime configuration in native image.
+ */
+package io.helidon.examples.logging.logback.aot;
diff --git a/examples/logging/logback-aot/src/main/resources/logback.xml b/examples/logging/logback-aot/src/main/resources/logback.xml
new file mode 100644
index 000000000..6d50d18e2
--- /dev/null
+++ b/examples/logging/logback-aot/src/main/resources/logback.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+ %d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg %X{name}%n
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/logging/pom.xml b/examples/logging/pom.xml
new file mode 100644
index 000000000..32f3f188d
--- /dev/null
+++ b/examples/logging/pom.xml
@@ -0,0 +1,45 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples
+ helidon-examples-project
+ 1.0.0-SNAPSHOT
+
+ io.helidon.examples.logging
+ helidon-examples-logging-project
+ 1.0.0-SNAPSHOT
+ Helidon Examples Logging
+ pom
+
+
+ Examples of Helidon Logging
+
+
+
+ jul
+ log4j
+ slf4j
+ logback-aot
+
+
diff --git a/examples/logging/slf4j/README.md b/examples/logging/slf4j/README.md
new file mode 100644
index 000000000..7ef0fcd5c
--- /dev/null
+++ b/examples/logging/slf4j/README.md
@@ -0,0 +1,45 @@
+Slf4j Example
+---
+
+This example shows how to use slf4j with MDC
+ using Helidon API.
+
+The example moves all Java Util Logging to slf4j
+
+The example can be built using GraalVM native image as well.
+
+Expected output should be similar to the following (for both hotspot and native):
+```text
+15:40:44.240 INFO [main] i.h.examples.logging.slf4j.Main - Starting up startup
+15:40:44.241 INFO [main] i.h.examples.logging.slf4j.Main - Using System logger startup
+15:40:44.245 INFO [pool-1-thread-1] i.h.examples.logging.slf4j.Main - Running on another thread propagated
+15:40:44.395 INFO [features-thread] i.h.common.features.HelidonFeatures - Helidon 4.0.0-SNAPSHOT features: [Config, Encoding, Media, WebServer]
+15:40:44.538 INFO [main] i.helidon.webserver.LoomServer - Started all channels in 15 milliseconds. 561 milliseconds since JVM startup. Java 20.0.1+9-29 propagated
+```
+
+# Running as jar
+
+Build this application:
+```shell
+mvn clean package
+```
+
+Run from command line:
+```shell
+java -jar target/helidon-examples-logging-slf4j.jar
+```
+
+# Running as native image
+You must use GraalVM with native image installed as your JDK,
+or you can specify an environment variable `GRAALVM_HOME` that points
+to such an installation.
+
+Build this application:
+```shell
+mvn clean package -Pnative-image
+```
+
+Run from command line:
+```shell
+./target/helidon-examples-logging-slf4j
+```
\ No newline at end of file
diff --git a/examples/logging/slf4j/pom.xml b/examples/logging/slf4j/pom.xml
new file mode 100644
index 000000000..7ce8af691
--- /dev/null
+++ b/examples/logging/slf4j/pom.xml
@@ -0,0 +1,78 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.logging
+ helidon-examples-logging-slf4j
+ 1.0.0-SNAPSHOT
+ Helidon Examples Logging Slf4j
+
+
+ Example of logging and MDC using Slf4j
+
+
+
+ io.helidon.examples.logging.slf4j.Main
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.logging
+ helidon-logging-slf4j
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.slf4j
+ jul-to-slf4j
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/logging/slf4j/src/main/java/io/helidon/examples/logging/slf4j/Main.java b/examples/logging/slf4j/src/main/java/io/helidon/examples/logging/slf4j/Main.java
new file mode 100644
index 000000000..35e59dcae
--- /dev/null
+++ b/examples/logging/slf4j/src/main/java/io/helidon/examples/logging/slf4j/Main.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.logging.slf4j;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import io.helidon.common.context.Context;
+import io.helidon.common.context.Contexts;
+import io.helidon.logging.common.HelidonMdc;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.http.HttpRouting;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+
+/**
+ * Main class of the example, runnable from command line.
+ */
+public final class Main {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
+ private static final System.Logger SYSTEM_LOGGER = System.getLogger(Main.class.getName());
+
+ private Main() {
+ }
+
+ /**
+ * Starts the example.
+ *
+ * @param args not used
+ */
+ public static void main(String[] args) {
+ LogConfig.configureRuntime();
+
+ // the Helidon context is used to propagate MDC across threads
+ // if running within Helidon WebServer, you do not need to runInContext, as that is already
+ // done by the webserver
+ Contexts.runInContext(Context.create(), Main::logging);
+
+ WebServer server = WebServer.builder()
+ .routing(Main::routing)
+ .build()
+ .start();
+ }
+
+ private static void routing(HttpRouting.Builder routing) {
+ routing.get("/", (req, res) -> {
+ HelidonMdc.set("name", String.valueOf(req.id()));
+ LOGGER.info("Running in webserver, id:");
+ res.send("Hello");
+ });
+ }
+
+ private static void logging() {
+ HelidonMdc.set("name", "startup");
+ LOGGER.info("Starting up");
+ SYSTEM_LOGGER.log(System.Logger.Level.INFO, "Using System logger");
+
+ // now let's see propagation across executor service boundary, we can also use Log4j's ThreadContext
+ MDC.put("name", "propagated");
+ // wrap executor so it supports Helidon context, this is done for all built-in executors in Helidon
+ ExecutorService es = Contexts.wrap(Executors.newSingleThreadExecutor());
+
+ Future> submit = es.submit(Main::log);
+ try {
+ submit.get();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ es.shutdown();
+ }
+
+ private static void log() {
+ LOGGER.info("Running on another thread");
+ }
+}
diff --git a/examples/logging/slf4j/src/main/java/io/helidon/examples/logging/slf4j/package-info.java b/examples/logging/slf4j/src/main/java/io/helidon/examples/logging/slf4j/package-info.java
new file mode 100644
index 000000000..a0d60f40d
--- /dev/null
+++ b/examples/logging/slf4j/src/main/java/io/helidon/examples/logging/slf4j/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Slf4j example with Mapped diagnostics context (MDC) in Helidon.
+ */
+package io.helidon.examples.logging.slf4j;
diff --git a/examples/logging/slf4j/src/main/resources/logback.xml b/examples/logging/slf4j/src/main/resources/logback.xml
new file mode 100644
index 000000000..7a6f59f85
--- /dev/null
+++ b/examples/logging/slf4j/src/main/resources/logback.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+ true
+
+
+
+
+
+ %d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg %X{name}%n
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/logging/slf4j/src/main/resources/logging.properties b/examples/logging/slf4j/src/main/resources/logging.properties
new file mode 100644
index 000000000..5e4967d7c
--- /dev/null
+++ b/examples/logging/slf4j/src/main/resources/logging.properties
@@ -0,0 +1,18 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# register SLF4JBridgeHandler as handler for the j.u.l. root logger
+handlers=org.slf4j.bridge.SLF4JBridgeHandler
diff --git a/examples/media/multipart/README.md b/examples/media/multipart/README.md
new file mode 100644
index 000000000..eb7ee218b
--- /dev/null
+++ b/examples/media/multipart/README.md
@@ -0,0 +1,23 @@
+# Helidon SE MultiPart Example
+
+This example demonstrates how to use `MultiPartSupport` with both the `WebServer`
+ and `WebClient` APIs.
+
+This project implements a simple file service web application that supports uploading
+and downloading files. The unit test uses the `WebClient` API to test the endpoints.
+
+## Build
+
+```shell
+mvn package
+```
+
+## Run
+
+First, start the server:
+
+```shell
+java -jar target/helidon-examples-media-multipart.jar
+```
+
+Then open in your browser.
diff --git a/examples/media/multipart/pom.xml b/examples/media/multipart/pom.xml
new file mode 100644
index 000000000..bb65cc2bf
--- /dev/null
+++ b/examples/media/multipart/pom.xml
@@ -0,0 +1,105 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.media
+ helidon-examples-media-multipart
+ 1.0.0-SNAPSHOT
+ Helidon Examples Media Support Multipart
+
+
+ Example of a form based file upload.
+
+
+
+ io.helidon.examples.media.multipart.Main
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.http.media
+ helidon-http-media-multipart
+
+
+ io.helidon.http.media
+ helidon-http-media-jsonp
+
+
+ io.helidon.webserver
+ helidon-webserver-static-content
+
+
+ jakarta.json
+ jakarta.json-api
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+
+
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
new file mode 100644
index 000000000..78d31f5e3
--- /dev/null
+++ b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/FileService.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.media.multipart;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import io.helidon.common.media.type.MediaTypes;
+import io.helidon.http.ContentDisposition;
+import io.helidon.http.Header;
+import io.helidon.http.HeaderNames;
+import io.helidon.http.HeaderValues;
+import io.helidon.http.ServerResponseHeaders;
+import io.helidon.http.media.multipart.MultiPart;
+import io.helidon.http.media.multipart.ReadablePart;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+import jakarta.json.Json;
+import jakarta.json.JsonArrayBuilder;
+import jakarta.json.JsonBuilderFactory;
+
+import static io.helidon.http.Status.BAD_REQUEST_400;
+import static io.helidon.http.Status.MOVED_PERMANENTLY_301;
+import static io.helidon.http.Status.NOT_FOUND_404;
+
+/**
+ * File service.
+ */
+public final class FileService implements HttpService {
+ private static final Header UI_LOCATION = HeaderValues.createCached(HeaderNames.LOCATION, "/ui");
+ private final JsonBuilderFactory jsonFactory;
+ private final Path storage;
+
+ /**
+ * Create a new file upload service instance.
+ */
+ FileService() {
+ jsonFactory = Json.createBuilderFactory(Map.of());
+ storage = createStorage();
+ System.out.println("Storage: " + storage);
+ }
+
+ @Override
+ public void routing(HttpRules rules) {
+ rules.get("/", this::list)
+ .get("/{fname}", this::download)
+ .post("/", this::upload);
+ }
+
+ private static Path createStorage() {
+ try {
+ return Files.createTempDirectory("fileupload");
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static Stream listFiles(Path storage) {
+ try (Stream walk = Files.walk(storage)) {
+ return walk.filter(Files::isRegularFile)
+ .map(storage::relativize)
+ .map(Path::toString)
+ .toList()
+ .stream();
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static OutputStream newOutputStream(Path storage, String fname) {
+ try {
+ return Files.newOutputStream(storage.resolve(fname),
+ StandardOpenOption.CREATE,
+ StandardOpenOption.WRITE,
+ StandardOpenOption.TRUNCATE_EXISTING);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private void list(ServerRequest req, ServerResponse res) {
+ JsonArrayBuilder arrayBuilder = jsonFactory.createArrayBuilder();
+ listFiles(storage).forEach(arrayBuilder::add);
+ res.send(jsonFactory.createObjectBuilder().add("files", arrayBuilder).build());
+ }
+
+ private void download(ServerRequest req, ServerResponse res) {
+ Path filePath = storage.resolve(req.path().pathParameters().get("fname"));
+ if (!filePath.getParent().equals(storage)) {
+ res.status(BAD_REQUEST_400).send("Invalid file name");
+ return;
+ }
+ if (!Files.exists(filePath)) {
+ res.status(NOT_FOUND_404).send();
+ return;
+ }
+ if (!Files.isRegularFile(filePath)) {
+ res.status(BAD_REQUEST_400).send("Not a file");
+ return;
+ }
+ ServerResponseHeaders headers = res.headers();
+ headers.contentType(MediaTypes.APPLICATION_OCTET_STREAM);
+ headers.set(ContentDisposition.builder()
+ .filename(filePath.getFileName().toString())
+ .build());
+ res.send(filePath);
+ }
+
+ private void upload(ServerRequest req, ServerResponse res) {
+ MultiPart mp = req.content().as(MultiPart.class);
+
+ while (mp.hasNext()) {
+ ReadablePart part = mp.next();
+ if ("file[]".equals(URLDecoder.decode(part.name(), StandardCharsets.UTF_8))) {
+ try (InputStream in = part.inputStream(); OutputStream out = newOutputStream(storage, part.fileName().get())) {
+ in.transferTo(out);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to write content", e);
+ }
+ }
+ }
+
+ res.status(MOVED_PERMANENTLY_301)
+ .header(UI_LOCATION)
+ .send();
+ }
+}
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
new file mode 100644
index 000000000..e1a827e41
--- /dev/null
+++ b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/Main.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.media.multipart;
+
+import io.helidon.http.Header;
+import io.helidon.http.HeaderNames;
+import io.helidon.http.HeaderValues;
+import io.helidon.http.Status;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.staticcontent.StaticContentService;
+
+/**
+ * This application provides a simple file upload service with a UI to exercise multipart.
+ */
+public final class Main {
+ private static final Header UI_LOCATION = HeaderValues.createCached(HeaderNames.LOCATION, "/ui");
+
+ private Main() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args command line arguments, ignored
+ */
+ public static void main(String[] args) {
+ WebServer server = WebServer.builder()
+ .routing(Main::routing)
+ .port(8080)
+ .build()
+ .start();
+
+ System.out.println("WEB server is up! http://localhost:" + server.port());
+ }
+
+ /**
+ * Updates the routing rules.
+ *
+ * @param rules routing rules
+ */
+ static void routing(HttpRules rules) {
+ rules.any("/", (req, res) -> {
+ res.status(Status.MOVED_PERMANENTLY_301);
+ res.header(UI_LOCATION);
+ res.send();
+ })
+ .register("/ui", StaticContentService.builder("WEB")
+ .welcomeFileName("index.html")
+ .build())
+ .register("/api", new FileService());
+ }
+}
diff --git a/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/package-info.java b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/package-info.java
new file mode 100644
index 000000000..6479921e5
--- /dev/null
+++ b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 Examples Media MultiPart.
+ */
+package io.helidon.examples.media.multipart;
diff --git a/examples/media/multipart/src/main/resources/WEB/index.html b/examples/media/multipart/src/main/resources/WEB/index.html
new file mode 100644
index 000000000..d7acca73f
--- /dev/null
+++ b/examples/media/multipart/src/main/resources/WEB/index.html
@@ -0,0 +1,66 @@
+
+
+
+
+
+ Helidon Examples Media Multipart
+
+
+
+
+
+Uploaded files
+
+
+Upload (buffered)
+
+
+Upload (stream)
+
+
+
+
+
diff --git a/examples/media/multipart/src/main/resources/logging.properties b/examples/media/multipart/src/main/resources/logging.properties
new file mode 100644
index 000000000..ccdbe8a09
--- /dev/null
+++ b/examples/media/multipart/src/main/resources/logging.properties
@@ -0,0 +1,21 @@
+#
+# Copyright (c) 2018, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+handlers=java.util.logging.ConsoleHandler
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS.%1$tL %5$s%6$s%n
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+io.helidon.webserver.level=INFO
diff --git a/examples/media/multipart/src/test/java/io/helidon/examples/media/multipart/FileServiceTest.java b/examples/media/multipart/src/test/java/io/helidon/examples/media/multipart/FileServiceTest.java
new file mode 100644
index 000000000..0f3f2926c
--- /dev/null
+++ b/examples/media/multipart/src/test/java/io/helidon/examples/media/multipart/FileServiceTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.media.multipart;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import io.helidon.common.media.type.MediaTypes;
+import io.helidon.http.HeaderNames;
+import io.helidon.http.Status;
+import io.helidon.http.media.multipart.WriteableMultiPart;
+import io.helidon.http.media.multipart.WriteablePart;
+import io.helidon.webclient.http1.Http1Client;
+import io.helidon.webclient.http1.Http1ClientResponse;
+import io.helidon.webserver.http.HttpRouting;
+import io.helidon.webserver.testing.junit5.ServerTest;
+import io.helidon.webserver.testing.junit5.SetUpRoute;
+
+import jakarta.json.JsonObject;
+import jakarta.json.JsonString;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Tests {@link FileService}.
+ */
+@TestMethodOrder(OrderAnnotation.class)
+@ServerTest
+public class FileServiceTest {
+ private final Http1Client client;
+
+ FileServiceTest(Http1Client client) {
+ this.client = client;
+ }
+
+ @SetUpRoute
+ static void routing(HttpRouting.Builder builder) {
+ Main.routing(builder);
+ }
+
+ @Test
+ @Order(1)
+ public void testUpload() throws IOException {
+ Path file = Files.writeString(Files.createTempFile(null, null), "bar\n");
+ try (Http1ClientResponse response = client.post("/api")
+ .followRedirects(false)
+ .submit(WriteableMultiPart.builder()
+ .addPart(writeablePart("file[]", "foo.txt", file))
+ .build())) {
+ assertThat(response.status(), is(Status.MOVED_PERMANENTLY_301));
+ }
+ }
+
+ @Test
+ @Order(2)
+ public void testStreamUpload() throws IOException {
+ Path file = Files.writeString(Files.createTempFile(null, null), "stream bar\n");
+ Path file2 = Files.writeString(Files.createTempFile(null, null), "stream foo\n");
+ try (Http1ClientResponse response = client.post("/api")
+ .queryParam("stream", "true")
+ .followRedirects(false)
+ .submit(WriteableMultiPart
+ .builder()
+ .addPart(writeablePart("file[]", "streamed-foo.txt", file))
+ .addPart(writeablePart("otherPart", "streamed-foo2.txt", file2))
+ .build())) {
+ assertThat(response.status(), is(Status.MOVED_PERMANENTLY_301));
+ }
+ }
+
+ @Test
+ @Order(3)
+ public void testList() {
+ try (Http1ClientResponse response = client.get("/api").request()) {
+ assertThat(response.status(), is(Status.OK_200));
+ JsonObject json = response.as(JsonObject.class);
+ assertThat(json, Matchers.is(notNullValue()));
+ List files = json.getJsonArray("files").getValuesAs(v -> ((JsonString) v).getString());
+ assertThat(files, hasItem("foo.txt"));
+ }
+ }
+
+ @Test
+ @Order(4)
+ public void testDownload() {
+ try (Http1ClientResponse response = client.get("/api").path("foo.txt").request()) {
+ assertThat(response.status(), is(Status.OK_200));
+ assertThat(response.headers().first(HeaderNames.CONTENT_DISPOSITION).orElse(null),
+ containsString("filename=\"foo.txt\""));
+ byte[] bytes = response.as(byte[].class);
+ assertThat(new String(bytes, StandardCharsets.UTF_8), Matchers.is("bar\n"));
+ }
+ }
+
+ private WriteablePart writeablePart(String partName, String fileName, Path filePath) throws IOException {
+ return WriteablePart.builder(partName)
+ .fileName(fileName)
+ .content(Files.readAllBytes(filePath))
+ .contentType(MediaTypes.MULTIPART_FORM_DATA)
+ .build();
+ }
+}
diff --git a/examples/media/pom.xml b/examples/media/pom.xml
new file mode 100644
index 000000000..69e2cfbe6
--- /dev/null
+++ b/examples/media/pom.xml
@@ -0,0 +1,41 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples
+ helidon-examples-project
+ 1.0.0-SNAPSHOT
+
+ io.helidon.examples.media
+ helidon-examples-media-project
+ Helidon Examples Media Support
+ pom
+
+
+ Examples of Helidon Media usage
+
+
+
+ multipart
+
+
diff --git a/examples/messaging/README.md b/examples/messaging/README.md
new file mode 100644
index 000000000..63201c10f
--- /dev/null
+++ b/examples/messaging/README.md
@@ -0,0 +1,55 @@
+# Helidon Messaging Examples
+
+## Prerequisites
+* Docker
+* Java 21+
+
+### Test Kafka server
+To make examples easily runnable,
+small, pocket size and pre-configured testing Kafka server Docker image is available.
+
+* To run it locally: `./kafkaRun.sh`
+ * Pre-configured topics:
+ * `messaging-test-topic-1`
+ * `messaging-test-topic-2`
+ * Stop it with `Ctrl+c`
+
+* Send messages manually with: `./kafkaProduce.sh [topic-name]`
+* Consume messages manually with: `./kafkaConsume.sh [topic-name]`
+
+### Test JMS server
+* Start ActiveMQ server locally:
+```shell
+docker run --name='activemq' --rm -p 61616:61616 -p 8161:8161 rmohr/activemq
+```
+
+### Test Oracle database
+* Start ActiveMQ server locally:
+```shell
+cd ./docker/oracle-aq-18-xe
+./buildAndRun.sh
+```
+
+For stopping Oracle database container use:
+```shell
+cd ./docker/oracle-aq-18-xe
+./stopAndClean.sh
+```
+
+## Helidon SE Reactive Messaging with Kafka Example
+For demonstration of Helidon SE Messaging with Kafka connector,
+continue to [Kafka with WebSocket SE Example](kafka-websocket-se/README.md)
+
+## Helidon MP Reactive Messaging with Kafka Example
+For demonstration of Helidon MP Messaging with Kafka connector,
+continue to [Kafka with WebSocket MP Example](kafka-websocket-mp/README.md)
+
+## Helidon MP Reactive Messaging with JMS Example
+For demonstration of Helidon MP Messaging with JMS connector,
+continue to [JMS with WebSocket MP Example](jms-websocket-mp/README.md)
+
+## Helidon MP Reactive Messaging with Oracle AQ Example
+For demonstration of Helidon MP Messaging with Oracle Advance Queueing connector,
+continue to [Oracle AQ with WebSocket MP Example](oracle-aq-websocket-mp/README.md)
+
+
diff --git a/examples/messaging/docker/kafka/Dockerfile.kafka b/examples/messaging/docker/kafka/Dockerfile.kafka
new file mode 100644
index 000000000..761977c96
--- /dev/null
+++ b/examples/messaging/docker/kafka/Dockerfile.kafka
@@ -0,0 +1,51 @@
+#
+# Copyright (c) 2019, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+FROM container-registry.oracle.com/java/jdk-no-fee-term:21
+
+ENV VERSION=2.7.0
+ENV SCALA_VERSION=2.13
+
+RUN dnf update && dnf -y install wget jq nc
+
+RUN REL_PATH=kafka/${VERSION}/kafka_${SCALA_VERSION}-${VERSION}.tgz \
+&& BACKUP_ARCHIVE=https://archive.apache.org/dist/ \
+&& echo "Looking for closest mirror ..." \
+&& MIRROR=$(curl -s 'https://www.apache.org/dyn/closer.cgi?as_json=1' | jq -r '.http[0]') \
+&& echo "Checking if version ${VERSION} is available on the mirror: ${MIRROR} ..." \
+&& MIRROR_RESPONSE=$(curl -L --write-out '%{http_code}' --silent --output /dev/null ${MIRROR}kafka/${VERSION}) \
+&& if [ $MIRROR_RESPONSE -eq 200 ]; then BIN_URL=${MIRROR}${REL_PATH}; else BIN_URL=${BACKUP_ARCHIVE}${REL_PATH}; fi \
+&& if [ $MIRROR_RESPONSE -ne 200 ]; then echo "Version ${VERSION} not found on the mirror ${MIRROR}, defaulting to archive ${BACKUP_ARCHIVE}."; fi \
+&& wget -q -O kafka.tar.gz ${BIN_URL} \
+&& tar -xzf kafka.tar.gz -C /opt && rm kafka.tar.gz \
+&& mv /opt/kafka* /opt/kafka
+
+WORKDIR /opt/kafka
+
+COPY start_kafka.sh start_kafka.sh
+COPY init_topics.sh init_topics.sh
+
+RUN chmod a+x ./*.sh
+
+RUN echo listener.security.protocol.map=INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT >> config/server.properties \
+&& echo advertised.listeners=INSIDE://localhost:9092,OUTSIDE://localhost:29092 >> config/server.properties \
+&& echo listeners=INSIDE://0.0.0.0:9092,OUTSIDE://0.0.0.0:29092 >> config/server.properties \
+&& echo inter.broker.listener.name=INSIDE >> config/server.properties
+
+# Expose Zookeeper and Kafka ports
+EXPOSE 2181 9092 29092
+
+CMD bash start_kafka.sh
diff --git a/examples/messaging/docker/kafka/init_topics.sh b/examples/messaging/docker/kafka/init_topics.sh
new file mode 100644
index 000000000..000ec3b53
--- /dev/null
+++ b/examples/messaging/docker/kafka/init_topics.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Wait for Kafka to start and create test topics:
+# topic messaging-test-topic-1 and topic messaging-test-topic-2
+#
+
+ZOOKEEPER_URL=localhost:2181
+KAFKA_TOPICS="/opt/kafka/bin/kafka-topics.sh --if-not-exists --zookeeper $ZOOKEEPER_URL"
+
+while sleep 2; do
+ brokers=$(echo dump | nc localhost 2181 | grep brokers | wc -l)
+ echo "Checking if Kafka is up: ${brokers}"
+ if [[ "$brokers" -gt "0" ]]; then
+ echo "KAFKA IS UP !!!"
+
+ echo "Creating test topics"
+ bash $KAFKA_TOPICS \
+ --create \
+ --replication-factor 1 \
+ --partitions 10 \
+ --topic messaging-test-topic-1
+ bash $KAFKA_TOPICS \
+ --create \
+ --replication-factor 1 \
+ --partitions 10 \
+ --topic messaging-test-topic-2
+ bash $KAFKA_TOPICS \
+ --create \
+ --replication-factor 1 \
+ --partitions 10 \
+ --config compression.type=snappy \
+ --topic messaging-test-topic-snappy-compressed
+ bash $KAFKA_TOPICS \
+ --create \
+ --replication-factor 1 \
+ --partitions 10 \
+ --config compression.type=lz4 \
+ --topic messaging-test-topic-lz4-compressed
+ bash $KAFKA_TOPICS \
+ --create \
+ --replication-factor 1 \
+ --partitions 10 \
+ --config compression.type=zstd \
+ --topic messaging-test-topic-zstd-compressed
+ bash $KAFKA_TOPICS \
+ --create \
+ --replication-factor 1 \
+ --partitions 10 \
+ --config compression.type=gzip \
+ --topic messaging-test-topic-gzip-compressed
+
+ echo
+ echo "Example topics created:"
+ echo " messaging-test-topic-1"
+ echo " messaging-test-topic-2"
+ echo " messaging-test-topic-snappy-compressed"
+ echo " messaging-test-topic-lz4-compressed"
+ echo " messaging-test-topic-zstd-compressed"
+ echo " messaging-test-topic-gzip-compressed"
+ echo
+ echo "================== Kafka is ready, stop it with Ctrl+C =================="
+ exit 0
+ fi
+done
diff --git a/examples/messaging/docker/kafka/start_kafka.sh b/examples/messaging/docker/kafka/start_kafka.sh
new file mode 100644
index 000000000..875987b55
--- /dev/null
+++ b/examples/messaging/docker/kafka/start_kafka.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Start Zookeeper, wait for it to come up and start Kafka.
+#
+
+# Allow ruok
+echo "4lw.commands.whitelist=*" >>/opt/kafka/config/zookeeper.properties
+
+# Start Zookeeper
+/opt/kafka/bin/zookeeper-server-start.sh /opt/kafka/config/zookeeper.properties &
+
+while sleep 2; do
+ isOk=$(echo ruok | nc localhost 2181)
+ echo "Checking if Zookeeper is up: ${isOk}"
+ if [ "${isOk}" = "imok" ]; then
+ echo "ZOOKEEPER IS UP !!!"
+ break
+ fi
+done
+
+# Create test topics when Kafka is ready
+/opt/kafka/init_topics.sh &
+
+# Start Kafka
+/opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/server.properties
+state=$?
+if [ $state -ne 0 ]; then
+ echo "Kafka stopped."
+ exit $state
+fi
+
+# Keep Kafka up till Ctrl+C
+read ;
diff --git a/examples/messaging/docker/oracle-aq-18-xe/Dockerfile b/examples/messaging/docker/oracle-aq-18-xe/Dockerfile
new file mode 100644
index 000000000..ed27c31b7
--- /dev/null
+++ b/examples/messaging/docker/oracle-aq-18-xe/Dockerfile
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+FROM oracle/database:18.4.0-xe as base
+
+
+COPY init.sql /docker-entrypoint-initdb.d/setup/
\ No newline at end of file
diff --git a/examples/messaging/docker/oracle-aq-18-xe/buildAndRun.sh b/examples/messaging/docker/oracle-aq-18-xe/buildAndRun.sh
new file mode 100755
index 000000000..0d9d3334c
--- /dev/null
+++ b/examples/messaging/docker/oracle-aq-18-xe/buildAndRun.sh
@@ -0,0 +1,102 @@
+#!/bin/bash
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+CURR_DIR=$(pwd)
+TEMP_DIR=../../target
+IMAGES_DIR=${TEMP_DIR}/ora-images
+COMMIT="a69fe9b08ff147bb746d16af76cc5279ea5baf7a";
+IMAGES_ZIP_URL=https://github.com/oracle/docker-images/archive/${COMMIT:0:7}.zip
+IMAGES_ZIP_DIR=docker-images-${COMMIT}/OracleDatabase/SingleInstance/dockerfiles
+ORA_DB_VERSION=18.4.0
+BASE_IMAGE_NAME=oracle/database:${ORA_DB_VERSION}-xe
+IMAGE_NAME=helidon/oracle-aq-example
+CONTAINER_NAME=oracle-aq-example
+ORACLE_PWD=frank
+
+printf "%-100s" "Checking if base image ${BASE_IMAGE_NAME} is available in local repository"
+if [[ "$(docker images -q ${BASE_IMAGE_NAME} 2>/dev/null)" == "" ]]; then
+ printf "NOK\n"
+
+ echo Base image ${BASE_IMAGE_NAME} not found. Building ...
+
+ # cleanup
+ mkdir -p ${TEMP_DIR}
+ rm -rf ${IMAGES_DIR}
+ rm -f ${TEMP_DIR}/ora-images.zip
+
+ # download official oracle docker images
+ curl -LJ -o ${TEMP_DIR}/ora-images.zip ${IMAGES_ZIP_URL}
+ # unzip only image for Oracle database 18.4.0
+ unzip -qq ${TEMP_DIR}/ora-images.zip "${IMAGES_ZIP_DIR}/*" -d ${IMAGES_DIR}
+ mv ${IMAGES_DIR}/${IMAGES_ZIP_DIR}/${ORA_DB_VERSION} ${IMAGES_DIR}/
+ mv ${IMAGES_DIR}/${IMAGES_ZIP_DIR}/buildContainerImage.sh ${IMAGES_DIR}/
+
+ # cleanup
+ rm -rf ${IMAGES_DIR}/docker-images-${COMMIT}
+ rm ${TEMP_DIR}/ora-images.zip
+
+ # build base image
+ # can take long(15 minutes or so)
+ cd ${IMAGES_DIR} || exit
+ bash ./buildContainerImage.sh -v ${ORA_DB_VERSION} -x || exit
+ cd ${CURR_DIR} || exit
+else
+ printf "OK\n"
+fi
+
+printf "%-100s" "Checking if image ${IMAGE_NAME} is available in local repository"
+if [[ "$(docker images -q ${IMAGE_NAME} 2>/dev/null)" == "" ]]; then
+ printf "NOK\n"
+
+ echo Image ${IMAGE_NAME} not found. Building ...
+ docker build -t ${IMAGE_NAME} . || exit
+else
+ printf "OK\n"
+fi
+
+printf "%-100s" "Checking if container ${CONTAINER_NAME} is ready"
+if [[ $(docker ps -a --filter "name=^/${CONTAINER_NAME}$" --format '{{.Names}}') != "${CONTAINER_NAME}" ]]; then
+ printf "NOK\n"
+
+ echo "Container ${CONTAINER_NAME} not found. Running ..."
+ echo "!!! Be aware first time database initialization can take tens of minutes."
+ echo "!!! Follow docker logs -f ${CONTAINER_NAME} for 'DATABASE IS READY TO USE' message"
+
+ docker run -d --name ${CONTAINER_NAME} \
+ -p 1521:1521 \
+ -p 5500:5500 \
+ -e ORACLE_PWD=${ORACLE_PWD} \
+ ${IMAGE_NAME} || exit
+else
+ printf "OK\n"
+ printf "%-100s" "Checking if container ${CONTAINER_NAME} is started"
+ if [[ $(docker ps --filter "name=^/${CONTAINER_NAME}$" --format '{{.Names}}') != "${CONTAINER_NAME}" ]]; then
+ printf "NOK\n"
+
+ echo "Container ${CONTAINER_NAME} not started. Starting ..."
+ docker start ${CONTAINER_NAME} || exit
+ else
+ printf "OK\n"
+ fi
+fi
+
+echo "Container ${CONTAINER_NAME} with Oracle database ${ORA_DB_VERSION} XE populated with example AQ queues is either started or starting."
+echo "For more info about the state of the database investigate logs:"
+echo " docker logs -f ${CONTAINER_NAME}"
+echo "Url: jdbc:oracle:thin:@localhost:1521:XE"
+echo "user: frank"
+echo "pass: frank"
diff --git a/examples/messaging/docker/oracle-aq-18-xe/examples.sql b/examples/messaging/docker/oracle-aq-18-xe/examples.sql
new file mode 100644
index 000000000..2e8dedc44
--- /dev/null
+++ b/examples/messaging/docker/oracle-aq-18-xe/examples.sql
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+-- SEND MESSAGE AS RAW BYTES
+DECLARE
+ id pls_integer;
+ enqueue_options DBMS_AQ.ENQUEUE_OPTIONS_T;
+ message_properties DBMS_AQ.MESSAGE_PROPERTIES_T;
+ message_handle RAW(16);
+ msg sys.aq$_jms_bytes_message;
+BEGIN
+ msg := sys.aq$_jms_bytes_message.construct;
+ id := msg.clear_body(-1);
+ msg.write_bytes(id, UTL_RAW.CAST_TO_RAW('Hello raw bytes!'));
+ msg.flush(id);
+ DBMS_AQ.ENQUEUE(
+ queue_name => 'FRANK.EXAMPLE_QUEUE_BYTES',
+ enqueue_options => enqueue_options,
+ message_properties => message_properties,
+ payload => msg,
+ msgid => message_handle);
+ COMMIT;
+END;
+
+-- SEND TEXT MESSAGE
+DECLARE
+ enqueue_options DBMS_AQ.ENQUEUE_OPTIONS_T;
+ message_properties DBMS_AQ.MESSAGE_PROPERTIES_T;
+ message_handle RAW(16);
+ msg SYS.AQ$_JMS_TEXT_MESSAGE;
+BEGIN
+ msg := SYS.AQ$_JMS_TEXT_MESSAGE.construct;
+ msg.set_text('Hello from PLSQL !');
+ DBMS_AQ.ENQUEUE(
+ queue_name => 'FRANK.EXAMPLE_QUEUE_1',
+ enqueue_options => enqueue_options,
+ message_properties => message_properties,
+ payload => msg,
+ msgid => message_handle);
+ COMMIT;
+END;
+
+-- SEND MAP MESSAGE
+DECLARE
+ id pls_integer;
+ enqueue_options DBMS_AQ.ENQUEUE_OPTIONS_T;
+ message_properties DBMS_AQ.MESSAGE_PROPERTIES_T;
+ message_handle RAW(16);
+ msg SYS.AQ$_JMS_MAP_MESSAGE;
+BEGIN
+ msg := SYS.AQ$_JMS_MAP_MESSAGE.construct;
+ id := msg.clear_body(-1);
+ msg.set_string(id, 'head', 'Hello');
+ msg.set_bytes(id, 'body', UTL_RAW.CAST_TO_RAW('this is map'));
+ msg.set_string(id, 'tail', 'message!');
+ msg.flush(id);
+ DBMS_AQ.ENQUEUE(
+ queue_name => 'FRANK.EXAMPLE_QUEUE_MAP',
+ enqueue_options => enqueue_options,
+ message_properties => message_properties,
+ payload => msg,
+ msgid => message_handle);
+ COMMIT;
+END;
\ No newline at end of file
diff --git a/examples/messaging/docker/oracle-aq-18-xe/init.sql b/examples/messaging/docker/oracle-aq-18-xe/init.sql
new file mode 100644
index 000000000..716c5e46d
--- /dev/null
+++ b/examples/messaging/docker/oracle-aq-18-xe/init.sql
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+alter session set "_ORACLE_SCRIPT"= true;
+create user frank identified by frank;
+grant dba to frank;
+
+grant execute on dbms_aq to frank;
+grant execute on dbms_aqadm to frank;
+grant execute on dbms_aqin to frank;
+
+CREATE OR REPLACE PROCEDURE create_queue(queueName IN VARCHAR2, qType IN VARCHAR2) IS
+BEGIN
+ dbms_aqadm.create_queue_table('FRANK.'||queueName||'_TAB', qType);
+ dbms_aqadm.create_queue('FRANK.'||queueName,'FRANK.'||queueName||'_TAB');
+ dbms_aqadm.start_queue('FRANK.'||queueName);
+END;
+/
+
+-- Setup example AQ queues FRANK.EXAMPLE_QUEUE_1, FRANK.EXAMPLE_QUEUE_2, FRANK.EXAMPLE_QUEUE_3
+begin
+ CREATE_QUEUE('example_queue_1', 'SYS.AQ$_JMS_TEXT_MESSAGE');
+ CREATE_QUEUE('example_queue_2', 'SYS.AQ$_JMS_TEXT_MESSAGE');
+ CREATE_QUEUE('example_queue_3', 'SYS.AQ$_JMS_TEXT_MESSAGE');
+ CREATE_QUEUE('example_queue_bytes', 'SYS.AQ$_JMS_BYTES_MESSAGE');
+ CREATE_QUEUE('example_queue_map', 'SYS.AQ$_JMS_MAP_MESSAGE');
+end;
+/
+
+-- Setup example table
+CREATE TABLE FRANK.MESSAGE_LOG (
+ id NUMBER(15) PRIMARY KEY,
+ message VARCHAR2(255) NOT NULL,
+ insert_date DATE DEFAULT (sysdate));
+COMMENT ON TABLE FRANK.MESSAGE_LOG IS 'Manually logged messages';
+
+CREATE SEQUENCE FRANK.MSG_LOG_SEQ START WITH 1;
+
+CREATE OR REPLACE TRIGGER MESSAGE_LOG_ID
+ BEFORE INSERT ON FRANK.MESSAGE_LOG
+ FOR EACH ROW
+
+BEGIN
+ SELECT FRANK.MSG_LOG_SEQ.NEXTVAL
+ INTO :new.id
+ FROM dual;
+END;
+/
diff --git a/examples/messaging/docker/oracle-aq-18-xe/stopAndClean.sh b/examples/messaging/docker/oracle-aq-18-xe/stopAndClean.sh
new file mode 100755
index 000000000..4ba522541
--- /dev/null
+++ b/examples/messaging/docker/oracle-aq-18-xe/stopAndClean.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+docker stop oracle-aq-example
+docker container rm oracle-aq-example
+docker image rm helidon/oracle-aq-example:latest
\ No newline at end of file
diff --git a/examples/messaging/jms-websocket-mp/README.md b/examples/messaging/jms-websocket-mp/README.md
new file mode 100644
index 000000000..bb3aeaa9e
--- /dev/null
+++ b/examples/messaging/jms-websocket-mp/README.md
@@ -0,0 +1,16 @@
+# Helidon Messaging with JMS Example
+
+## Prerequisites
+* Java 21+
+* Docker
+* [ActiveMQ server](../README.md) running on `localhost:61616`
+
+## Build & Run
+```shell
+#1.
+mvn clean package
+#2.
+ java -jar target/helidon-examples-jms-websocket-mp.jar
+```
+3. Visit http://localhost:7001
+
diff --git a/examples/messaging/jms-websocket-mp/pom.xml b/examples/messaging/jms-websocket-mp/pom.xml
new file mode 100644
index 000000000..424b01ca3
--- /dev/null
+++ b/examples/messaging/jms-websocket-mp/pom.xml
@@ -0,0 +1,75 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.jms
+ helidon-examples-jms-websocket-mp
+ 1.0.0-SNAPSHOT
+ Helidon Examples Messaging JMS WebSocket SE
+
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile
+
+
+ io.helidon.microprofile.messaging
+ helidon-microprofile-messaging
+
+
+ io.helidon.messaging.jms
+ helidon-messaging-jms
+
+
+ io.helidon.microprofile.websocket
+ helidon-microprofile-websocket
+
+
+ io.smallrye
+ jandex
+ runtime
+ true
+
+
+ org.apache.activemq
+ activemq-client
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/messaging/jms-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/MsgProcessingBean.java b/examples/messaging/jms-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/MsgProcessingBean.java
new file mode 100644
index 000000000..3c9134bfc
--- /dev/null
+++ b/examples/messaging/jms-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/MsgProcessingBean.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.messaging.mp;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.SubmissionPublisher;
+
+import io.helidon.common.reactive.Multi;
+import io.helidon.messaging.connectors.jms.JmsMessage;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import org.eclipse.microprofile.reactive.messaging.Incoming;
+import org.eclipse.microprofile.reactive.messaging.Message;
+import org.eclipse.microprofile.reactive.messaging.Outgoing;
+import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder;
+import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams;
+import org.reactivestreams.FlowAdapters;
+import org.reactivestreams.Publisher;
+
+/**
+ * Bean for message processing.
+ */
+@ApplicationScoped
+public class MsgProcessingBean {
+
+ private final SubmissionPublisher emitter = new SubmissionPublisher<>();
+ private final SubmissionPublisher broadCaster = new SubmissionPublisher<>();
+
+ /**
+ * Create a publisher for the emitter.
+ *
+ * @return A Publisher from the emitter
+ */
+ @Outgoing("multiplyVariants")
+ public Publisher preparePublisher() {
+ // Create new publisher for emitting to by this::process
+ return ReactiveStreams
+ .fromPublisher(FlowAdapters.toPublisher(Multi.create(emitter)))
+ .buildRs();
+ }
+
+ /**
+ * Returns a builder for a processor that maps a string into three variants.
+ *
+ * @return ProcessorBuilder
+ */
+ @Incoming("multiplyVariants")
+ @Outgoing("toJms")
+ public ProcessorBuilder> multiply() {
+ // Multiply to 3 variants of same message
+ return ReactiveStreams.builder()
+ .flatMap(o ->
+ ReactiveStreams.of(
+ // upper case variant
+ o.toUpperCase(),
+ // repeat twice variant
+ o.repeat(2),
+ // reverse chars 'tnairav'
+ new StringBuilder(o).reverse().toString())
+ ).map(Message::of);
+ }
+
+ /**
+ * Broadcasts an event.
+ *
+ * @param msg Message to broadcast
+ * @return completed stage
+ */
+ @Incoming("fromJms")
+ public CompletionStage broadcast(JmsMessage msg) {
+ // Broadcast to all subscribers
+ broadCaster.submit(msg.getPayload());
+ return CompletableFuture.completedFuture(null);
+ }
+
+ /**
+ * Same JMS session, different connector.
+ *
+ * @param msg Message to broadcast
+ * @return completed stage
+ */
+ @Incoming("fromJmsSameSession")
+ public CompletionStage sameSession(JmsMessage msg) {
+ // Broadcast to all subscribers
+ broadCaster.submit(msg.getPayload());
+ return CompletableFuture.completedFuture(null);
+ }
+
+ /**
+ * Subscribe new Multi to broadcasting publisher.
+ *
+ * @return new Multi subscribed to broadcaster
+ */
+ public Multi subscribeMulti() {
+ return Multi.create(broadCaster);
+ }
+
+ /**
+ * Emit a message.
+ *
+ * @param msg message to emit
+ */
+ public void process(final String msg) {
+ emitter.submit(msg);
+ }
+}
diff --git a/examples/messaging/jms-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/SendingResource.java b/examples/messaging/jms-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/SendingResource.java
new file mode 100644
index 000000000..f68eaf9ca
--- /dev/null
+++ b/examples/messaging/jms-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/SendingResource.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.messaging.mp;
+
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+/**
+ * Expose send method for publishing to messaging.
+ */
+@Path("rest/messages")
+@RequestScoped
+public class SendingResource {
+ private final MsgProcessingBean msgBean;
+
+ /**
+ * Constructor injection of field values.
+ *
+ * @param msgBean Messaging example bean
+ */
+ @Inject
+ public SendingResource(MsgProcessingBean msgBean) {
+ this.msgBean = msgBean;
+ }
+
+ /**
+ * Send message through Messaging to JMS.
+ *
+ * @param msg message to process
+ */
+ @Path("/send/{msg}")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public void getSend(@PathParam("msg") String msg) {
+ msgBean.process(msg);
+ }
+}
diff --git a/examples/messaging/jms-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/WebSocketEndpoint.java b/examples/messaging/jms-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/WebSocketEndpoint.java
new file mode 100644
index 000000000..ea760722b
--- /dev/null
+++ b/examples/messaging/jms-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/WebSocketEndpoint.java
@@ -0,0 +1,95 @@
+
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.messaging.mp;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import io.helidon.common.reactive.Single;
+
+import jakarta.inject.Inject;
+import jakarta.websocket.CloseReason;
+import jakarta.websocket.EndpointConfig;
+import jakarta.websocket.OnClose;
+import jakarta.websocket.OnOpen;
+import jakarta.websocket.Session;
+import jakarta.websocket.server.ServerEndpoint;
+
+/**
+ * Register all WebSocket connection as subscribers
+ * of broadcasting {@link java.util.concurrent.SubmissionPublisher}
+ * in the {@link MsgProcessingBean}.
+ *
+ * When connection is closed, cancel subscription and remove reference.
+ */
+@ServerEndpoint("/ws/messages")
+public class WebSocketEndpoint {
+
+ private static final Logger LOGGER = Logger.getLogger(WebSocketEndpoint.class.getName());
+
+ private final Map> subscriberRegister = new HashMap<>();
+
+ @Inject
+ private MsgProcessingBean msgProcessingBean;
+
+ /**
+ * On WebSocket session is opened.
+ *
+ * @param session web socket session
+ * @param endpointConfig endpoint config
+ */
+ @OnOpen
+ public void onOpen(Session session, EndpointConfig endpointConfig) {
+ System.out.println("New WebSocket client connected with session " + session.getId());
+
+ Single single = msgProcessingBean.subscribeMulti()
+ // Watch for errors coming from upstream
+ .onError(throwable -> LOGGER.log(Level.SEVERE, "Upstream error!", throwable))
+ // Send every item coming from upstream over web socket
+ .forEach(s -> sendTextMessage(session, s));
+
+ //Save forEach single promise for later cancellation
+ subscriberRegister.put(session.getId(), single);
+ }
+
+ /**
+ * When WebSocket session is closed.
+ *
+ * @param session web socket session
+ * @param closeReason web socket close reason
+ */
+ @OnClose
+ public void onClose(final Session session, final CloseReason closeReason) {
+ LOGGER.info("Closing session " + session.getId());
+ // Properly unsubscribe from SubmissionPublisher
+ Optional.ofNullable(subscriberRegister.remove(session.getId()))
+ .ifPresent(Single::cancel);
+ }
+
+ private void sendTextMessage(Session session, String msg) {
+ try {
+ session.getBasicRemote().sendText(msg);
+ } catch (IOException e) {
+ LOGGER.log(Level.SEVERE, "Message sending over WebSocket failed", e);
+ }
+ }
+}
diff --git a/examples/messaging/jms-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/package-info.java b/examples/messaging/jms-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/package-info.java
new file mode 100644
index 000000000..d2ddab5ba
--- /dev/null
+++ b/examples/messaging/jms-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/package-info.java
@@ -0,0 +1,21 @@
+
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Reactive Messaging JMS example.
+ */
+package io.helidon.examples.messaging.mp;
diff --git a/examples/messaging/jms-websocket-mp/src/main/resources/META-INF/beans.xml b/examples/messaging/jms-websocket-mp/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..676e09a2d
--- /dev/null
+++ b/examples/messaging/jms-websocket-mp/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/messaging/jms-websocket-mp/src/main/resources/WEB/favicon.ico b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/favicon.ico
new file mode 100644
index 000000000..d91659fdb
Binary files /dev/null and b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/favicon.ico differ
diff --git a/examples/messaging/jms-websocket-mp/src/main/resources/WEB/img/arrow-1.png b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/img/arrow-1.png
new file mode 100644
index 000000000..bbba0aef8
Binary files /dev/null and b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/img/arrow-1.png differ
diff --git a/examples/messaging/jms-websocket-mp/src/main/resources/WEB/img/arrow-2.png b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/img/arrow-2.png
new file mode 100644
index 000000000..0b1096b07
Binary files /dev/null and b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/img/arrow-2.png differ
diff --git a/examples/messaging/jms-websocket-mp/src/main/resources/WEB/img/cloud.png b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/img/cloud.png
new file mode 100644
index 000000000..3e04833c0
Binary files /dev/null and b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/img/cloud.png differ
diff --git a/examples/messaging/jms-websocket-mp/src/main/resources/WEB/img/frank.png b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/img/frank.png
new file mode 100644
index 000000000..51a13d8db
Binary files /dev/null and b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/img/frank.png differ
diff --git a/examples/messaging/jms-websocket-mp/src/main/resources/WEB/index.html b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/index.html
new file mode 100644
index 000000000..b35aed463
--- /dev/null
+++ b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/index.html
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+ Helidon Reactive Messaging
+
+
+
+
+
+
+
+
+
+
+
+
+
+
REST call /rest/messages/send/{msg}
+
+
+
Messages received from JMS over websocket
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/messaging/jms-websocket-mp/src/main/resources/WEB/main.css b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/main.css
new file mode 100644
index 000000000..8e4a7dcec
--- /dev/null
+++ b/examples/messaging/jms-websocket-mp/src/main/resources/WEB/main.css
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#root {
+ background-color: #36ABF2;
+ font-family: Roboto,sans-serif;
+ color: #fff;
+ position: absolute;
+ overflow-x: hidden;
+ -ms-overflow-style: none; /* Internet Explorer 10+ */
+ scrollbar-width: none; /* Firefox */
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+#root::-webkit-scrollbar {
+ display: none; /* Safari and Chrome */
+}
+
+#helidon {
+ width: 509px;
+ height: 273px;
+ position: relative;
+ left: -509px;
+ z-index: 4;
+ background: url('img/frank.png');
+}
+
+#rest-tip {
+ position: relative;
+ top: -80px;
+ left: 160px;
+}
+
+#rest-tip-arrow {
+ width: 205px;
+ height: 304px;
+ z-index: 4;
+ top: -20px;
+ background: url('img/arrow-1.png');
+}
+#rest-tip-label {
+ position: absolute;
+ white-space: nowrap;
+ font-size: 18px;
+ font-weight: bold;
+ z-index: 4;
+ left: -60px;
+}
+
+#sse-tip {
+ position: absolute;
+ overflow: hidden;
+ display: flex;
+ width: auto;
+ height: auto;
+ top: 5%;
+ right: 10%;
+ z-index: 0;
+}
+
+#sse-tip-arrow {
+ position: relative;
+ top: -30px;
+ width: 296px;
+ height: 262px;
+ z-index: 4;
+ background: url('img/arrow-2.png');
+}
+#sse-tip-label {
+ position: relative;
+ white-space: nowrap;
+ font-size: 18px;
+ font-weight: bold;
+ z-index: 4;
+}
+
+#producer {
+ float: left;
+ position: relative;
+ width: 300px;
+ height: 100%;
+ margin: 50px;
+ padding: 10px;
+ z-index: 99;
+}
+
+#msgBox {
+ position: absolute;
+ width: 300px;
+ top: 25%;
+ right: 3%;
+ height: 100%;
+ margin: 50px;
+ padding: 10px;
+ z-index: 20;
+}
+
+#input {
+ width: 210px;
+ height: 22px;
+ top: 58px;
+ left: 30px;
+ background-color: white;
+ border-radius: 10px;
+ border-style: solid;
+ border-color: white;
+ position: absolute;
+ z-index: 10;
+}
+
+#inputCloud {
+ position: relative;
+ width: 310px;
+ height: 150px;
+ background: url('img/cloud.png');
+}
+
+#msg {
+ background-color: #D2EBFC;
+ color: #1A9BF4;
+ border-radius: 10px;
+ width: 300px;
+ height: 50px;
+ margin: 5px;
+ display: flex;
+ padding-left: 10px;
+ justify-content: center;
+ align-items: center;
+ z-index: 99;
+}
+
+#submit {
+ font-weight: bold;
+ background-color: aqua;
+ color: #1A9BF4;
+ border-radius: 12px;
+ width: 100px;
+ height: 30px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin: 5px;
+ cursor: pointer;
+}
+
+#snippet {
+ position: absolute;
+ top: 15%;
+ left: 30%;
+ width: 40%;
+ z-index: 5;
+}
+
+.hljs {
+ border-radius: 10px;
+ font-size: 12px;
+}
diff --git a/examples/messaging/jms-websocket-mp/src/main/resources/application.yaml b/examples/messaging/jms-websocket-mp/src/main/resources/application.yaml
new file mode 100644
index 000000000..803e8862e
--- /dev/null
+++ b/examples/messaging/jms-websocket-mp/src/main/resources/application.yaml
@@ -0,0 +1,49 @@
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server:
+ port: 7001
+ host: 0.0.0.0
+ static.classpath:
+ location: /WEB
+ welcome: index.html
+
+mp.messaging:
+ connector.helidon-jms:
+ jndi:
+ jms-factory: ConnectionFactory
+ env-properties:
+ java.naming.factory.initial: org.apache.activemq.jndi.ActiveMQInitialContextFactory
+ java.naming.provider.url: tcp://127.0.0.1:61616
+
+ outgoing:
+ toJms:
+ connector: helidon-jms
+ destination: messaging-queue-topic-2
+ type: queue
+
+ incoming:
+ fromJms:
+ connector: helidon-jms
+ destination: messaging-test-queue-1
+ session-group-id: session-group-1
+ type: queue
+
+ fromJmsSameSession:
+ connector: helidon-jms
+ destination: messaging-queue-topic-2
+ session-group-id: session-group-1
+ type: queue
diff --git a/examples/messaging/jms-websocket-mp/src/main/resources/logging.properties b/examples/messaging/jms-websocket-mp/src/main/resources/logging.properties
new file mode 100644
index 000000000..361bf5f6c
--- /dev/null
+++ b/examples/messaging/jms-websocket-mp/src/main/resources/logging.properties
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+# Component specific log levels
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
diff --git a/examples/messaging/jms-websocket-se/README.md b/examples/messaging/jms-websocket-se/README.md
new file mode 100644
index 000000000..4d8eabba4
--- /dev/null
+++ b/examples/messaging/jms-websocket-se/README.md
@@ -0,0 +1,16 @@
+# Helidon Messaging with JMS Example
+
+## Prerequisites
+* Java 21+
+* Docker
+* [ActiveMQ server](../README.md) running on `localhost:61616`
+
+## Build & Run
+```shell
+#1.
+mvn clean package
+#2.
+java -jar target/helidon-examples-jms-websocket-se.jar
+```
+3. Visit http://localhost:7001
+
diff --git a/examples/messaging/jms-websocket-se/pom.xml b/examples/messaging/jms-websocket-se/pom.xml
new file mode 100644
index 000000000..80065a32e
--- /dev/null
+++ b/examples/messaging/jms-websocket-se/pom.xml
@@ -0,0 +1,81 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.jms
+ helidon-examples-jms-websocket-se
+ 1.0.0-SNAPSHOT
+ Helidon Examples Messaging JMS WebSocket SE
+
+
+ io.helidon.examples.messaging.se.Main
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.webserver
+ helidon-webserver-static-content
+
+
+ io.helidon.webserver
+ helidon-webserver-websocket
+
+
+ io.helidon.messaging
+ helidon-messaging
+
+
+ io.helidon.messaging.jms
+ helidon-messaging-jms
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ org.apache.activemq
+ activemq-client
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/messaging/jms-websocket-se/src/main/java/io/helidon/examples/messaging/se/Main.java b/examples/messaging/jms-websocket-se/src/main/java/io/helidon/examples/messaging/se/Main.java
new file mode 100644
index 000000000..2d46ed705
--- /dev/null
+++ b/examples/messaging/jms-websocket-se/src/main/java/io/helidon/examples/messaging/se/Main.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.messaging.se;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.LogManager;
+
+import io.helidon.config.Config;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.staticcontent.StaticContentService;
+import io.helidon.webserver.websocket.WsRouting;
+
+/**
+ * The application main class.
+ */
+public final class Main {
+
+ /**
+ * Cannot be instantiated.
+ */
+ private Main() {
+ }
+
+ /**
+ * Application main entry point.
+ *
+ * @param args command line arguments.
+ * @throws IOException if there are problems reading logging properties
+ */
+ public static void main(final String[] args) throws IOException {
+ startServer();
+ }
+
+ /**
+ * Start the server.
+ *
+ * @return the created {@link WebServer} instance
+ * @throws IOException if there are problems reading logging properties
+ */
+ static WebServer startServer() throws IOException {
+ // load logging configuration
+ setupLogging();
+
+ // By default, this will pick up application.yaml from the classpath
+ Config config = Config.create();
+
+ SendingService sendingService = new SendingService(config);
+
+ WebServer server = WebServer.builder()
+ .routing(routing -> routing
+ // register static content support (on "/")
+ .register(StaticContentService.builder("/WEB")
+ .welcomeFileName("index.html")
+ .build())
+ // register rest endpoint for sending to Jms
+ .register("/rest/messages", sendingService))
+ .addRouting(WsRouting.builder()
+ .endpoint("/ws/messages", new WebSocketEndpoint()))
+ .config(config.get("server"))
+ .build().start();
+
+ System.out.println("WEB server is up! http://localhost:" + server.port());
+ Runtime.getRuntime().addShutdownHook(new Thread(sendingService::shutdown));
+
+ // Server threads are not daemon. No need to block. Just react.
+ return server;
+ }
+
+ /**
+ * Configure logging from logging.properties file.
+ */
+ private static void setupLogging() throws IOException {
+ try (InputStream is = Main.class.getResourceAsStream("/logging.properties")) {
+ LogManager.getLogManager().readConfiguration(is);
+ }
+ }
+}
diff --git a/examples/messaging/jms-websocket-se/src/main/java/io/helidon/examples/messaging/se/SendingService.java b/examples/messaging/jms-websocket-se/src/main/java/io/helidon/examples/messaging/se/SendingService.java
new file mode 100644
index 000000000..457f9c5f7
--- /dev/null
+++ b/examples/messaging/jms-websocket-se/src/main/java/io/helidon/examples/messaging/se/SendingService.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.messaging.se;
+
+import io.helidon.config.Config;
+import io.helidon.messaging.Channel;
+import io.helidon.messaging.Emitter;
+import io.helidon.messaging.Messaging;
+import io.helidon.messaging.connectors.jms.JmsConnector;
+import io.helidon.messaging.connectors.jms.Type;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+
+import org.apache.activemq.jndi.ActiveMQInitialContextFactory;
+
+class SendingService implements HttpService {
+
+ private final Emitter emitter;
+ private final Messaging messaging;
+
+ SendingService(Config config) {
+
+ String url = config.get("app.jms.url").asString().get();
+ String destination = config.get("app.jms.destination").asString().get();
+
+ // Prepare channel for connecting processor -> jms connector with specific subscriber configuration,
+ // channel -> connector mapping is automatic when using JmsConnector.configBuilder()
+ Channel toJms = Channel.builder()
+ .subscriberConfig(JmsConnector.configBuilder()
+ .jndiInitialFactory(ActiveMQInitialContextFactory.class.getName())
+ .jndiProviderUrl(url)
+ .type(Type.QUEUE)
+ .destination(destination)
+ .build())
+ .build();
+
+ // Prepare channel for connecting emitter -> processor
+ Channel toProcessor = Channel.create();
+
+ // Prepare Jms connector, can be used by any channel
+ JmsConnector jmsConnector = JmsConnector.create();
+
+ // Prepare emitter for manual publishing to channel
+ emitter = Emitter.create(toProcessor);
+
+ // Transforming to upper-case before sending to jms
+ messaging = Messaging.builder()
+ .emitter(emitter)
+ // Processor connect two channels together
+ .processor(toProcessor, toJms, String::toUpperCase)
+ .connector(jmsConnector)
+ .build()
+ .start();
+ }
+
+ /**
+ * A service registers itself by updating the routing rules.
+ *
+ * @param rules the routing rules.
+ */
+ @Override
+ public void routing(HttpRules rules) {
+ // Listen for GET /example/send/{msg}
+ // to send it through messaging to Jms
+ rules.get("/send/{msg}", (req, res) -> {
+ String msg = req.path().pathParameters().get("msg");
+ System.out.println("Emitting: " + msg);
+ emitter.send(msg);
+ res.send();
+ });
+ }
+
+ /**
+ * Gracefully terminate messaging.
+ */
+ public void shutdown() {
+ messaging.stop();
+ }
+}
diff --git a/examples/messaging/jms-websocket-se/src/main/java/io/helidon/examples/messaging/se/WebSocketEndpoint.java b/examples/messaging/jms-websocket-se/src/main/java/io/helidon/examples/messaging/se/WebSocketEndpoint.java
new file mode 100644
index 000000000..4b9d0f6a1
--- /dev/null
+++ b/examples/messaging/jms-websocket-se/src/main/java/io/helidon/examples/messaging/se/WebSocketEndpoint.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.messaging.se;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import io.helidon.config.Config;
+import io.helidon.messaging.Channel;
+import io.helidon.messaging.Messaging;
+import io.helidon.messaging.connectors.jms.JmsConnector;
+import io.helidon.messaging.connectors.jms.Type;
+import io.helidon.websocket.WsListener;
+import io.helidon.websocket.WsSession;
+
+import org.apache.activemq.jndi.ActiveMQInitialContextFactory;
+
+/**
+ * WebSocket endpoint.
+ */
+public class WebSocketEndpoint implements WsListener {
+
+ private static final Logger LOGGER = Logger.getLogger(WebSocketEndpoint.class.getName());
+
+ private final Map messagingRegister = new HashMap<>();
+ private final Config config = Config.create();
+
+ @Override
+ public void onOpen(WsSession session) {
+ System.out.println("Session " + session);
+
+ String url = config.get("app.jms.url").asString().get();
+ String destination = config.get("app.jms.destination").asString().get();
+
+ // Prepare channel for connecting jms connector with specific publisher configuration -> listener,
+ // channel -> connector mapping is automatic when using JmsConnector.configBuilder()
+ Channel fromJms = Channel.builder()
+ .name("from-jms")
+ .publisherConfig(JmsConnector.configBuilder()
+ .jndiInitialFactory(ActiveMQInitialContextFactory.class.getName())
+ .jndiProviderUrl(url)
+ .type(Type.QUEUE)
+ .destination(destination)
+ .build())
+ .build();
+
+ // Prepare Jms connector, can be used by any channel
+ JmsConnector jmsConnector = JmsConnector.create();
+
+ Messaging messaging = Messaging.builder()
+ .connector(jmsConnector)
+ .listener(fromJms, payload -> {
+ System.out.println("Jms says: " + payload);
+ session.send(payload, false);
+ })
+ .build()
+ .start();
+
+ //Save the messaging instance for proper shutdown
+ // when websocket connection is terminated
+ messagingRegister.put(session, messaging);
+ }
+
+ @Override
+ public void onClose(WsSession session, int status, String reason) {
+ LOGGER.info("Closing session " + session);
+ // Properly stop messaging when websocket connection is terminated
+ Optional.ofNullable(messagingRegister.remove(session))
+ .ifPresent(Messaging::stop);
+ }
+}
diff --git a/examples/messaging/jms-websocket-se/src/main/java/io/helidon/examples/messaging/se/package-info.java b/examples/messaging/jms-websocket-se/src/main/java/io/helidon/examples/messaging/se/package-info.java
new file mode 100644
index 000000000..0dca31628
--- /dev/null
+++ b/examples/messaging/jms-websocket-se/src/main/java/io/helidon/examples/messaging/se/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 Reactive Messaging with Jms Example.
+ */
+package io.helidon.examples.messaging.se;
diff --git a/examples/messaging/jms-websocket-se/src/main/resources/WEB/favicon.ico b/examples/messaging/jms-websocket-se/src/main/resources/WEB/favicon.ico
new file mode 100644
index 000000000..d91659fdb
Binary files /dev/null and b/examples/messaging/jms-websocket-se/src/main/resources/WEB/favicon.ico differ
diff --git a/examples/messaging/jms-websocket-se/src/main/resources/WEB/img/arrow-1.png b/examples/messaging/jms-websocket-se/src/main/resources/WEB/img/arrow-1.png
new file mode 100644
index 000000000..bbba0aef8
Binary files /dev/null and b/examples/messaging/jms-websocket-se/src/main/resources/WEB/img/arrow-1.png differ
diff --git a/examples/messaging/jms-websocket-se/src/main/resources/WEB/img/arrow-2.png b/examples/messaging/jms-websocket-se/src/main/resources/WEB/img/arrow-2.png
new file mode 100644
index 000000000..0b1096b07
Binary files /dev/null and b/examples/messaging/jms-websocket-se/src/main/resources/WEB/img/arrow-2.png differ
diff --git a/examples/messaging/jms-websocket-se/src/main/resources/WEB/img/cloud.png b/examples/messaging/jms-websocket-se/src/main/resources/WEB/img/cloud.png
new file mode 100644
index 000000000..3e04833c0
Binary files /dev/null and b/examples/messaging/jms-websocket-se/src/main/resources/WEB/img/cloud.png differ
diff --git a/examples/messaging/jms-websocket-se/src/main/resources/WEB/img/frank.png b/examples/messaging/jms-websocket-se/src/main/resources/WEB/img/frank.png
new file mode 100644
index 000000000..51a13d8db
Binary files /dev/null and b/examples/messaging/jms-websocket-se/src/main/resources/WEB/img/frank.png differ
diff --git a/examples/messaging/jms-websocket-se/src/main/resources/WEB/index.html b/examples/messaging/jms-websocket-se/src/main/resources/WEB/index.html
new file mode 100644
index 000000000..94e417ab6
--- /dev/null
+++ b/examples/messaging/jms-websocket-se/src/main/resources/WEB/index.html
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+ Helidon Reactive Messaging
+
+
+
+
+
+
+
+
+
+
+
+
+
+
REST call /rest/messages/send/{msg}
+
+
+
Messages received from Jms over websocket
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/messaging/jms-websocket-se/src/main/resources/WEB/main.css b/examples/messaging/jms-websocket-se/src/main/resources/WEB/main.css
new file mode 100644
index 000000000..8e4a7dcec
--- /dev/null
+++ b/examples/messaging/jms-websocket-se/src/main/resources/WEB/main.css
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#root {
+ background-color: #36ABF2;
+ font-family: Roboto,sans-serif;
+ color: #fff;
+ position: absolute;
+ overflow-x: hidden;
+ -ms-overflow-style: none; /* Internet Explorer 10+ */
+ scrollbar-width: none; /* Firefox */
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+#root::-webkit-scrollbar {
+ display: none; /* Safari and Chrome */
+}
+
+#helidon {
+ width: 509px;
+ height: 273px;
+ position: relative;
+ left: -509px;
+ z-index: 4;
+ background: url('img/frank.png');
+}
+
+#rest-tip {
+ position: relative;
+ top: -80px;
+ left: 160px;
+}
+
+#rest-tip-arrow {
+ width: 205px;
+ height: 304px;
+ z-index: 4;
+ top: -20px;
+ background: url('img/arrow-1.png');
+}
+#rest-tip-label {
+ position: absolute;
+ white-space: nowrap;
+ font-size: 18px;
+ font-weight: bold;
+ z-index: 4;
+ left: -60px;
+}
+
+#sse-tip {
+ position: absolute;
+ overflow: hidden;
+ display: flex;
+ width: auto;
+ height: auto;
+ top: 5%;
+ right: 10%;
+ z-index: 0;
+}
+
+#sse-tip-arrow {
+ position: relative;
+ top: -30px;
+ width: 296px;
+ height: 262px;
+ z-index: 4;
+ background: url('img/arrow-2.png');
+}
+#sse-tip-label {
+ position: relative;
+ white-space: nowrap;
+ font-size: 18px;
+ font-weight: bold;
+ z-index: 4;
+}
+
+#producer {
+ float: left;
+ position: relative;
+ width: 300px;
+ height: 100%;
+ margin: 50px;
+ padding: 10px;
+ z-index: 99;
+}
+
+#msgBox {
+ position: absolute;
+ width: 300px;
+ top: 25%;
+ right: 3%;
+ height: 100%;
+ margin: 50px;
+ padding: 10px;
+ z-index: 20;
+}
+
+#input {
+ width: 210px;
+ height: 22px;
+ top: 58px;
+ left: 30px;
+ background-color: white;
+ border-radius: 10px;
+ border-style: solid;
+ border-color: white;
+ position: absolute;
+ z-index: 10;
+}
+
+#inputCloud {
+ position: relative;
+ width: 310px;
+ height: 150px;
+ background: url('img/cloud.png');
+}
+
+#msg {
+ background-color: #D2EBFC;
+ color: #1A9BF4;
+ border-radius: 10px;
+ width: 300px;
+ height: 50px;
+ margin: 5px;
+ display: flex;
+ padding-left: 10px;
+ justify-content: center;
+ align-items: center;
+ z-index: 99;
+}
+
+#submit {
+ font-weight: bold;
+ background-color: aqua;
+ color: #1A9BF4;
+ border-radius: 12px;
+ width: 100px;
+ height: 30px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin: 5px;
+ cursor: pointer;
+}
+
+#snippet {
+ position: absolute;
+ top: 15%;
+ left: 30%;
+ width: 40%;
+ z-index: 5;
+}
+
+.hljs {
+ border-radius: 10px;
+ font-size: 12px;
+}
diff --git a/examples/messaging/jms-websocket-se/src/main/resources/application.yaml b/examples/messaging/jms-websocket-se/src/main/resources/application.yaml
new file mode 100644
index 000000000..38d4767c5
--- /dev/null
+++ b/examples/messaging/jms-websocket-se/src/main/resources/application.yaml
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+app:
+ jms:
+ url: tcp://127.0.0.1:61616
+ destination: se-example-queue-1
+
+server:
+ port: 7001
+ host: 0.0.0.0
+ static:
+ classpath:
+ location: /WEB
+ welcome: index.html
diff --git a/examples/messaging/jms-websocket-se/src/main/resources/logging.properties b/examples/messaging/jms-websocket-se/src/main/resources/logging.properties
new file mode 100644
index 000000000..361bf5f6c
--- /dev/null
+++ b/examples/messaging/jms-websocket-se/src/main/resources/logging.properties
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+# Component specific log levels
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
diff --git a/examples/messaging/kafka-websocket-mp/README.md b/examples/messaging/kafka-websocket-mp/README.md
new file mode 100644
index 000000000..75269ca5c
--- /dev/null
+++ b/examples/messaging/kafka-websocket-mp/README.md
@@ -0,0 +1,15 @@
+# Helidon MP Reactive Messaging with Kafka Example
+
+## Prerequisites
+* Docker
+* Java 21+
+* [Kafka bootstrap server](../README.md) running on `localhost:9092`
+
+## Build & Run
+```shell
+#1.
+mvn clean package
+#2.
+java -jar target/kafka-websocket-mp.jar
+```
+3. Visit http://localhost:7001
\ No newline at end of file
diff --git a/examples/messaging/kafka-websocket-mp/pom.xml b/examples/messaging/kafka-websocket-mp/pom.xml
new file mode 100644
index 000000000..e21075aa4
--- /dev/null
+++ b/examples/messaging/kafka-websocket-mp/pom.xml
@@ -0,0 +1,80 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.1.0-SNAPSHOT
+
+ io.helidon.examples.messaging.mp
+ kafka-websocket-mp
+ 1.0.0-SNAPSHOT
+ Helidon Examples Messaging Kafka WebSocket MP
+
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile
+
+
+ io.helidon.microprofile.messaging
+ helidon-microprofile-messaging
+
+
+ io.helidon.messaging.kafka
+ helidon-messaging-kafka
+
+
+ io.helidon.microprofile.websocket
+ helidon-microprofile-websocket
+
+
+ io.smallrye
+ jandex
+ runtime
+ true
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
diff --git a/examples/messaging/kafka-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/MsgProcessingBean.java b/examples/messaging/kafka-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/MsgProcessingBean.java
new file mode 100644
index 000000000..6ed4d2c01
--- /dev/null
+++ b/examples/messaging/kafka-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/MsgProcessingBean.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.messaging.mp;
+
+import java.util.concurrent.SubmissionPublisher;
+
+import io.helidon.common.reactive.Multi;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import org.eclipse.microprofile.reactive.messaging.Incoming;
+import org.eclipse.microprofile.reactive.messaging.Message;
+import org.eclipse.microprofile.reactive.messaging.Outgoing;
+import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder;
+import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams;
+import org.reactivestreams.FlowAdapters;
+import org.reactivestreams.Publisher;
+
+/**
+ * Bean for message processing.
+ */
+@ApplicationScoped
+public class MsgProcessingBean {
+
+ private final SubmissionPublisher emitter = new SubmissionPublisher<>();
+ private final SubmissionPublisher broadCaster = new SubmissionPublisher<>();
+
+ /**
+ * Create a publisher for the emitter.
+ *
+ * @return A Publisher from the emitter
+ */
+ @Outgoing("multiplyVariants")
+ public Publisher preparePublisher() {
+ // Create new publisher for emitting to by this::process
+ return ReactiveStreams
+ .fromPublisher(FlowAdapters.toPublisher(Multi.create(emitter)))
+ .buildRs();
+ }
+
+ /**
+ * Returns a builder for a processor that maps a string into three variants.
+ *
+ * @return ProcessorBuilder
+ */
+ @Incoming("multiplyVariants")
+ @Outgoing("toKafka")
+ public ProcessorBuilder> multiply() {
+ // Multiply to 3 variants of same message
+ return ReactiveStreams.builder()
+ .flatMap(o ->
+ ReactiveStreams.of(
+ // upper case variant
+ o.toUpperCase(),
+ // repeat twice variant
+ o.repeat(2),
+ // reverse chars 'tnairav'
+ new StringBuilder(o).reverse().toString())
+ ).map(Message::of);
+ }
+
+ /**
+ * Broadcasts an event.
+ *
+ * @param msg Message to broadcast
+ */
+ @Incoming("fromKafka")
+ public void broadcast(String msg) {
+ // Broadcast to all subscribers
+ broadCaster.submit(msg);
+ }
+
+ /**
+ * Subscribe new Multi to broadcasting publisher.
+ *
+ * @return new Multi subscribed to broadcaster
+ */
+ public Multi subscribeMulti() {
+ return Multi.create(broadCaster);
+ }
+
+ /**
+ * Emit a message.
+ *
+ * @param msg message to emit
+ */
+ public void process(final String msg) {
+ emitter.submit(msg);
+ }
+}
diff --git a/examples/messaging/kafka-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/SendingResource.java b/examples/messaging/kafka-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/SendingResource.java
new file mode 100644
index 000000000..78ec04ee7
--- /dev/null
+++ b/examples/messaging/kafka-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/SendingResource.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.messaging.mp;
+
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+/**
+ * Expose send method for publishing to messaging.
+ */
+@Path("rest/messages")
+@RequestScoped
+public class SendingResource {
+ private final MsgProcessingBean msgBean;
+
+ /**
+ * Constructor injection of field values.
+ *
+ * @param msgBean Messaging example bean
+ */
+ @Inject
+ public SendingResource(MsgProcessingBean msgBean) {
+ this.msgBean = msgBean;
+ }
+
+
+ /**
+ * Send message through Messaging to Kafka.
+ *
+ * @param msg message to process
+ */
+ @Path("/send/{msg}")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public void getSend(@PathParam("msg") String msg) {
+ msgBean.process(msg);
+ }
+}
diff --git a/examples/messaging/kafka-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/WebSocketEndpoint.java b/examples/messaging/kafka-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/WebSocketEndpoint.java
new file mode 100644
index 000000000..ea760722b
--- /dev/null
+++ b/examples/messaging/kafka-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/WebSocketEndpoint.java
@@ -0,0 +1,95 @@
+
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.examples.messaging.mp;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import io.helidon.common.reactive.Single;
+
+import jakarta.inject.Inject;
+import jakarta.websocket.CloseReason;
+import jakarta.websocket.EndpointConfig;
+import jakarta.websocket.OnClose;
+import jakarta.websocket.OnOpen;
+import jakarta.websocket.Session;
+import jakarta.websocket.server.ServerEndpoint;
+
+/**
+ * Register all WebSocket connection as subscribers
+ * of broadcasting {@link java.util.concurrent.SubmissionPublisher}
+ * in the {@link MsgProcessingBean}.
+ *
+ * When connection is closed, cancel subscription and remove reference.
+ */
+@ServerEndpoint("/ws/messages")
+public class WebSocketEndpoint {
+
+ private static final Logger LOGGER = Logger.getLogger(WebSocketEndpoint.class.getName());
+
+ private final Map> subscriberRegister = new HashMap<>();
+
+ @Inject
+ private MsgProcessingBean msgProcessingBean;
+
+ /**
+ * On WebSocket session is opened.
+ *
+ * @param session web socket session
+ * @param endpointConfig endpoint config
+ */
+ @OnOpen
+ public void onOpen(Session session, EndpointConfig endpointConfig) {
+ System.out.println("New WebSocket client connected with session " + session.getId());
+
+ Single single = msgProcessingBean.subscribeMulti()
+ // Watch for errors coming from upstream
+ .onError(throwable -> LOGGER.log(Level.SEVERE, "Upstream error!", throwable))
+ // Send every item coming from upstream over web socket
+ .forEach(s -> sendTextMessage(session, s));
+
+ //Save forEach single promise for later cancellation
+ subscriberRegister.put(session.getId(), single);
+ }
+
+ /**
+ * When WebSocket session is closed.
+ *
+ * @param session web socket session
+ * @param closeReason web socket close reason
+ */
+ @OnClose
+ public void onClose(final Session session, final CloseReason closeReason) {
+ LOGGER.info("Closing session " + session.getId());
+ // Properly unsubscribe from SubmissionPublisher
+ Optional.ofNullable(subscriberRegister.remove(session.getId()))
+ .ifPresent(Single::cancel);
+ }
+
+ private void sendTextMessage(Session session, String msg) {
+ try {
+ session.getBasicRemote().sendText(msg);
+ } catch (IOException e) {
+ LOGGER.log(Level.SEVERE, "Message sending over WebSocket failed", e);
+ }
+ }
+}
diff --git a/examples/messaging/kafka-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/package-info.java b/examples/messaging/kafka-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/package-info.java
new file mode 100644
index 000000000..30fb29b39
--- /dev/null
+++ b/examples/messaging/kafka-websocket-mp/src/main/java/io/helidon/examples/messaging/mp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 Reactive Messaging with Kafka Example.
+ */
+package io.helidon.examples.messaging.mp;
diff --git a/examples/messaging/kafka-websocket-mp/src/main/resources/META-INF/beans.xml b/examples/messaging/kafka-websocket-mp/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..676e09a2d
--- /dev/null
+++ b/examples/messaging/kafka-websocket-mp/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/messaging/kafka-websocket-mp/src/main/resources/META-INF/microprofile-config.properties b/examples/messaging/kafka-websocket-mp/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 000000000..ff628b571
--- /dev/null
+++ b/examples/messaging/kafka-websocket-mp/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2020, 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+server.port=7001
+server.host=0.0.0.0
+server.static.classpath.location=/WEB
+server.static.classpath.welcome=index.html
+
+# Configure channel fromKafka to ask Kafka connector for publisher
+mp.messaging.incoming.fromKafka.connector=helidon-kafka
+mp.messaging.incoming.fromKafka.enable.auto.commit=true
+mp.messaging.incoming.fromKafka.group.id=websocket-mp-example-1
+
+# Configure channel toKafka to ask Kafka connector for subscriber
+mp.messaging.outgoing.toKafka.connector=helidon-kafka
+
+# Connector config properties are common to all channels
+mp.messaging.connector.helidon-kafka.bootstrap.servers=localhost:9092
+mp.messaging.connector.helidon-kafka.topic=messaging-test-topic-1
+mp.messaging.connector.helidon-kafka.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
+mp.messaging.connector.helidon-kafka.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
+mp.messaging.connector.helidon-kafka.key.serializer=org.apache.kafka.common.serialization.StringSerializer
+mp.messaging.connector.helidon-kafka.value.serializer=org.apache.kafka.common.serialization.StringSerializer
diff --git a/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/favicon.ico b/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/favicon.ico
new file mode 100644
index 000000000..d91659fdb
Binary files /dev/null and b/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/favicon.ico differ
diff --git a/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/img/arrow-1.png b/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/img/arrow-1.png
new file mode 100644
index 000000000..bbba0aef8
Binary files /dev/null and b/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/img/arrow-1.png differ
diff --git a/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/img/arrow-2.png b/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/img/arrow-2.png
new file mode 100644
index 000000000..0b1096b07
Binary files /dev/null and b/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/img/arrow-2.png differ
diff --git a/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/img/cloud.png b/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/img/cloud.png
new file mode 100644
index 000000000..3e04833c0
Binary files /dev/null and b/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/img/cloud.png differ
diff --git a/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/img/frank.png b/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/img/frank.png
new file mode 100644
index 000000000..51a13d8db
Binary files /dev/null and b/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/img/frank.png differ
diff --git a/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/index.html b/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/index.html
new file mode 100644
index 000000000..de0d967fc
--- /dev/null
+++ b/examples/messaging/kafka-websocket-mp/src/main/resources/WEB/index.html
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+ Helidon Reactive Messaging
+
+
+
+
+
+
+
+
+