diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index e47d6233..5bb6310e 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -63,7 +63,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Download Maven Artifacts
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
name: io-helidon-maven-artifacts
path: ~/.m2/repository/io/helidon
@@ -82,7 +82,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Download Maven Artifacts
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
name: io-helidon-maven-artifacts
path: ~/.m2/repository/io/helidon
@@ -104,7 +104,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Download Maven Artifacts
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
name: io-helidon-maven-artifacts
path: ~/.m2/repository/io/helidon
diff --git a/README.md b/README.md
index ebc85642..a3a54264 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,15 @@
# Helidon Examples
-Examples for Helidon 3.
+Examples for Helidon 2.
[Helidon 4 Examples](https://github.com/helidon-io/helidon/tree/main/examples) and [Helidon 2 Examples](https://github.com/helidon-io/helidon/tree/helidon-2.x/examples) are in the primary Helidon repository.
## How to Run
-To build and run Helidon 3 examples you need:
+To build and run Helidon 2 examples you need:
-* Java 17 or later
+* Java 11 or later
* Maven 3.6.1 or later
Then:
@@ -17,7 +17,7 @@ Then:
```
git clone https://github.com/helidon-io/helidon-examples.git
cd helidon-examples
-git checkout helidon-3.x
+git checkout helidon-2.x
mvn clean install
```
@@ -26,22 +26,24 @@ mvn clean install
| Branch | Description |
| ------------- |-------------|
| helidon-3.x | Examples for the current release of Helidon 3 |
+| helidon-2.x | Examples for the current release of Helidon 2 |
| dev-3.x | Development branch for Helidon 3 release currently under development |
+| dev-2.x | Development branch for Helidon 2 release currently under development |
| Tags | Description |
| ------------- |-------------|
| N.N.N | Examples for a specific version of Helion |
-To checkout examples for the most recent release of Helidon 3:
+To checkout examples for the most recent release of Helidon 2:
```
-git checkout helidon-3.x
+git checkout helidon-2.x
```
To checkout examples for a specific release of Helidon:
```
-git checkout tags/3.2.5
+git checkout tags/2.X.Y
```
## Documentation
diff --git a/etc/checkstyle-suppressions.xml b/etc/checkstyle-suppressions.xml
index 227144ca..e2a0d475 100644
--- a/etc/checkstyle-suppressions.xml
+++ b/etc/checkstyle-suppressions.xml
@@ -1,7 +1,7 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 00000000..6720e2a3
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,38 @@
+
+
+
+
+# Helidon Examples
+
+Welcome to the Helidon Examples! If this is your first experience with
+Helidon we recommend you start with our
+[quickstart](https://helidon.io/docs/v2/#/about/03_prerequisites)
+That will quickly get you going with your first Helidon application.
+
+After that you can come back here and dig into the examples.
+
+Our examples are Maven projects and can be built and run with
+Java 11 or newer -- so make sure you have those:
+
+```
+java -version
+mvn -version
+```
+
+# Building an Example
+
+Each example has a `README` that you will follow. To build most examples
+just `cd` to the directory and run `mvn package`:
+
+```
+cd examples/microprofile/hello-world-explicit
+mvn package
+```
+
+Usually the example will produce an application jar file that you can run:
+
+```
+java -jar target/example-name.jar
+```
+
+But always see the example's `README` for details.
diff --git a/examples/config/README.md b/examples/config/README.md
new file mode 100644
index 00000000..01555f33
--- /dev/null
+++ b/examples/config/README.md
@@ -0,0 +1,5 @@
+
+# Helidon SE Config Examples
+
+Each subdirectory contains example code that highlights specific aspects of
+Helidon configuration.
\ No newline at end of file
diff --git a/examples/config/basics/README.md b/examples/config/basics/README.md
new file mode 100644
index 00000000..e9478efd
--- /dev/null
+++ b/examples/config/basics/README.md
@@ -0,0 +1,16 @@
+# Helidon Config Basic Example
+
+This example shows the basics of using Helidon SE Config. The
+[Main.java](./src/main/java/io/helidon/config/examples/basics/Main.java) class shows:
+
+* loading configuration from a resource
+[`application.conf`](./src/main/resources/application.conf) on the classpath
+containing config in HOCON (Human-Optimized Config Object Notation) format
+* getting configuration values of various types
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-config-basics.jar
+```
diff --git a/examples/config/basics/pom.xml b/examples/config/basics/pom.xml
new file mode 100644
index 00000000..b1c0a168
--- /dev/null
+++ b/examples/config/basics/pom.xml
@@ -0,0 +1,66 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 2.6.8-SNAPSHOT
+
+
+ io.helidon.examples.config
+ helidon-examples-config-basics
+ 1.0.0-SNAPSHOT
+ Helidon Config Examples Basics
+
+
+ The simplest example shows how to use Configuration API.
+
+
+
+ io.helidon.config.examples.basics.Main
+
+
+
+
+ io.helidon.config
+ helidon-config
+
+
+ io.helidon.config
+ helidon-config-hocon
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/config/basics/src/main/java/io/helidon/config/examples/basics/Main.java b/examples/config/basics/src/main/java/io/helidon/config/examples/basics/Main.java
new file mode 100644
index 00000000..cdd3a179
--- /dev/null
+++ b/examples/config/basics/src/main/java/io/helidon/config/examples/basics/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.config.examples.basics;
+
+import java.nio.file.Path;
+import java.util.List;
+
+import io.helidon.config.Config;
+
+import static io.helidon.config.ConfigSources.classpath;
+
+/**
+ * Basics example.
+ */
+public class Main {
+
+ private Main() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args arguments
+ */
+ public static void main(String... args) {
+ Config config = Config.create(classpath("application.conf"));
+
+ int pageSize = config.get("app.page-size").asInt().get();
+
+ boolean storageEnabled = config.get("app.storageEnabled").asBoolean().orElse(false);
+
+ List 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/config/examples/basics/package-info.java b/examples/config/basics/src/main/java/io/helidon/config/examples/basics/package-info.java
new file mode 100644
index 00000000..e0abe866
--- /dev/null
+++ b/examples/config/basics/src/main/java/io/helidon/config/examples/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.config.examples.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 00000000..8f283613
--- /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 00000000..721cc5fc
--- /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 00000000..0b955c80
--- /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/config/examples/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/config/examples/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 00000000..69d8030e
--- /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 00000000..f2166b73
--- /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 00000000..5bbaf875
--- /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 00000000..1ce97e36
--- /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 00000000..5dddeced
--- /dev/null
+++ b/examples/config/changes/pom.xml
@@ -0,0 +1,70 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 2.6.8-SNAPSHOT
+
+
+ io.helidon.examples.config
+ helidon-examples-config-changes
+ 1.0.0-SNAPSHOT
+ Helidon Config Examples Changes
+
+
+ The example shows how to use Configuration Changes API.
+
+
+
+ io.helidon.config.examples.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/config/examples/changes/AsSupplierExample.java b/examples/config/changes/src/main/java/io/helidon/config/examples/changes/AsSupplierExample.java
new file mode 100644
index 00000000..4b980863
--- /dev/null
+++ b/examples/config/changes/src/main/java/io/helidon/config/examples/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.config.examples.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/config/examples/changes/Main.java b/examples/config/changes/src/main/java/io/helidon/config/examples/changes/Main.java
new file mode 100644
index 00000000..1a0fd510
--- /dev/null
+++ b/examples/config/changes/src/main/java/io/helidon/config/examples/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.config.examples.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/config/examples/changes/OnChangeExample.java b/examples/config/changes/src/main/java/io/helidon/config/examples/changes/OnChangeExample.java
new file mode 100644
index 00000000..fd024a63
--- /dev/null
+++ b/examples/config/changes/src/main/java/io/helidon/config/examples/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.config.examples.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("changeit").asString().get());
+ }
+
+}
diff --git a/examples/config/changes/src/main/java/io/helidon/config/examples/changes/package-info.java b/examples/config/changes/src/main/java/io/helidon/config/examples/changes/package-info.java
new file mode 100644
index 00000000..af0290d4
--- /dev/null
+++ b/examples/config/changes/src/main/java/io/helidon/config/examples/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.config.examples.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 00000000..32f1207f
--- /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 00000000..1e6cab22
--- /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 00000000..b808ffd7
--- /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/config/examples/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 00000000..c1885143
--- /dev/null
+++ b/examples/config/git/pom.xml
@@ -0,0 +1,83 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 2.6.8-SNAPSHOT
+
+
+ io.helidon.examples.config
+ helidon-examples-config-git
+ 1.0.0-SNAPSHOT
+ Helidon Config Examples Git
+
+
+ The example shows how to use GitConfigSource.
+
+
+
+ io.helidon.config.examples.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/config/examples/git/Main.java b/examples/config/git/src/main/java/io/helidon/config/examples/git/Main.java
new file mode 100644
index 00000000..34298d59
--- /dev/null
+++ b/examples/config/git/src/main/java/io/helidon/config/examples/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.config.examples.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/config/examples/git/package-info.java b/examples/config/git/src/main/java/io/helidon/config/examples/git/package-info.java
new file mode 100644
index 00000000..04d3cd41
--- /dev/null
+++ b/examples/config/git/src/main/java/io/helidon/config/examples/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.config.examples.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 00000000..721cc5fc
--- /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 00000000..8acf17ad
--- /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/config/examples/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/config/examples/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/config/examples/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 00000000..be11a743
--- /dev/null
+++ b/examples/config/mapping/pom.xml
@@ -0,0 +1,70 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 2.6.8-SNAPSHOT
+
+
+ io.helidon.examples.config
+ helidon-examples-config-mapping
+ 1.0.0-SNAPSHOT
+ Helidon Config Examples Mapping
+
+
+ The example shows how to use Config Mapping functionality.
+
+
+
+ io.helidon.config.examples.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/config/examples/mapping/BuilderExample.java b/examples/config/mapping/src/main/java/io/helidon/config/examples/mapping/BuilderExample.java
new file mode 100644
index 00000000..1d281668
--- /dev/null
+++ b/examples/config/mapping/src/main/java/io/helidon/config/examples/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.config.examples.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/config/examples/mapping/DeserializationExample.java b/examples/config/mapping/src/main/java/io/helidon/config/examples/mapping/DeserializationExample.java
new file mode 100644
index 00000000..d17de4e0
--- /dev/null
+++ b/examples/config/mapping/src/main/java/io/helidon/config/examples/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.config.examples.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/config/examples/mapping/FactoryMethodExample.java b/examples/config/mapping/src/main/java/io/helidon/config/examples/mapping/FactoryMethodExample.java
new file mode 100644
index 00000000..64ef361a
--- /dev/null
+++ b/examples/config/mapping/src/main/java/io/helidon/config/examples/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.config.examples.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/config/examples/mapping/Main.java b/examples/config/mapping/src/main/java/io/helidon/config/examples/mapping/Main.java
new file mode 100644
index 00000000..7e704669
--- /dev/null
+++ b/examples/config/mapping/src/main/java/io/helidon/config/examples/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.config.examples.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/config/examples/mapping/package-info.java b/examples/config/mapping/src/main/java/io/helidon/config/examples/mapping/package-info.java
new file mode 100644
index 00000000..ab515f0b
--- /dev/null
+++ b/examples/config/mapping/src/main/java/io/helidon/config/examples/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.config.examples.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 00000000..3699dcd8
--- /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 00000000..721cc5fc
--- /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 00000000..7057c721
--- /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 00000000..2f30e19c
--- /dev/null
+++ b/examples/config/metadata/pom.xml
@@ -0,0 +1,67 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 2.6.8-SNAPSHOT
+
+
+ io.helidon.examples.config
+ helidon-examples-config-metadata
+ 1.0.0-SNAPSHOT
+ Helidon Config Examples Metadata
+
+
+ This example shows possibilities with configuration metadata. To test this, add a configurable library on the classpath
+ and run the example.
+
+
+
+ io.helidon.config.examples.metadata.ConfigMetadataMain
+
+
+
+
+ org.glassfish
+ jakarta.json
+
+
+ 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/config/examples/metadata/ConfigMetadataMain.java b/examples/config/metadata/src/main/java/io/helidon/config/examples/metadata/ConfigMetadataMain.java
new file mode 100644
index 00000000..a7b244d9
--- /dev/null
+++ b/examples/config/metadata/src/main/java/io/helidon/config/examples/metadata/ConfigMetadataMain.java
@@ -0,0 +1,462 @@
+/*
+ * 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.config.examples.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 javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import javax.json.JsonReaderFactory;
+import javax.json.JsonValue;
+
+import io.helidon.config.examples.metadata.ConfiguredType.ConfiguredProperty;
+import io.helidon.config.metadata.ConfiguredOption;
+
+/**
+ * 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 (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(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(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(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(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(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(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 (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()) {
+ 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(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(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(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/config/examples/metadata/ConfiguredType.java b/examples/config/metadata/src/main/java/io/helidon/config/examples/metadata/ConfiguredType.java
new file mode 100644
index 00000000..c36f64b0
--- /dev/null
+++ b/examples/config/metadata/src/main/java/io/helidon/config/examples/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.config.examples.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 javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+
+import io.helidon.config.metadata.ConfiguredOption;
+
+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/config/examples/metadata/package-info.java b/examples/config/metadata/src/main/java/io/helidon/config/examples/metadata/package-info.java
new file mode 100644
index 00000000..4c8928eb
--- /dev/null
+++ b/examples/config/metadata/src/main/java/io/helidon/config/examples/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.config.examples.metadata;
diff --git a/examples/config/overrides/README.md b/examples/config/overrides/README.md
new file mode 100644
index 00000000..966a6435
--- /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 00000000..3ff3dbc0
--- /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 00000000..70b33f38
--- /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 00000000..b716bcdf
--- /dev/null
+++ b/examples/config/overrides/pom.xml
@@ -0,0 +1,62 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 2.6.8-SNAPSHOT
+
+
+ io.helidon.examples.config
+ helidon-examples-config-overrides
+ 1.0.0-SNAPSHOT
+ Helidon Config Examples Overrides
+
+
+ The example shows how to use Overrides in Configuration API.
+
+
+
+ io.helidon.config.examples.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/config/examples/overrides/Main.java b/examples/config/overrides/src/main/java/io/helidon/config/examples/overrides/Main.java
new file mode 100644
index 00000000..278b9ff4
--- /dev/null
+++ b/examples/config/overrides/src/main/java/io/helidon/config/examples/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.config.examples.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:
+ *
+ * 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/config/examples/overrides/package-info.java b/examples/config/overrides/src/main/java/io/helidon/config/examples/overrides/package-info.java
new file mode 100644
index 00000000..6cc89740
--- /dev/null
+++ b/examples/config/overrides/src/main/java/io/helidon/config/examples/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.config.examples.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 00000000..b13e7ad0
--- /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 00000000..721cc5fc
--- /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 00000000..232d4f28
--- /dev/null
+++ b/examples/config/pom.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples
+ helidon-examples-project
+ 1.0.0-SNAPSHOT
+
+ io.helidon.examples.config
+ helidon-examples-config-project
+ pom
+ Helidon Config Examples
+
+
+ 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 00000000..8c98e330
--- /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 00000000..f0cf375c
--- /dev/null
+++ b/examples/config/profiles/config-profile-prod.yaml
@@ -0,0 +1,22 @@
+#
+# Copyright (c) 2021 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 00000000..ee020122
--- /dev/null
+++ b/examples/config/profiles/config/config-prod.yaml
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2021 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 00000000..41046355
--- /dev/null
+++ b/examples/config/profiles/pom.xml
@@ -0,0 +1,66 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 2.6.8-SNAPSHOT
+
+
+ io.helidon.examples.config
+ helidon-examples-config-profiles
+ 1.0.0-SNAPSHOT
+ Helidon Config Examples 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 00000000..0077a169
--- /dev/null
+++ b/examples/config/profiles/src/main/java/io/helidon/examples/config/profiles/Main.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 00000000..bb7cb9ee
--- /dev/null
+++ b/examples/config/profiles/src/main/java/io/helidon/examples/config/profiles/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 00000000..f54597e9
--- /dev/null
+++ b/examples/config/profiles/src/main/resources/application-local.yaml
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2021 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 00000000..7343ff57
--- /dev/null
+++ b/examples/config/profiles/src/main/resources/application-stage.yaml
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2021 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 00000000..3eee73c1
--- /dev/null
+++ b/examples/config/profiles/src/main/resources/application.yaml
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2021 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 00000000..345d2f00
--- /dev/null
+++ b/examples/config/profiles/src/main/resources/config-profile-dev.yaml
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2021 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 00000000..9740180c
--- /dev/null
+++ b/examples/config/profiles/src/main/resources/config-profile-stage.yaml
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2021 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 00000000..e7afc2db
--- /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/config/examples/sources/DirectorySourceExample.java)
+reads configuration from multiple files in a directory by specifying only the directory.
+2. [`LoadSourcesExample.java`](./src/main/java/io/helidon/config/examples/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/config/examples/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 00000000..4b08deac
--- /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 00000000..312f8d84
--- /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 00000000..31e31fe2
--- /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 00000000..5bbaf875
--- /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 00000000..1ce97e36
--- /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 00000000..76778642
--- /dev/null
+++ b/examples/config/sources/pom.xml
@@ -0,0 +1,66 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 2.6.8-SNAPSHOT
+
+
+ io.helidon.examples.config
+ helidon-examples-config-sources
+ 1.0.0-SNAPSHOT
+ Helidon Config Examples Sources
+
+
+ This example shows how to merge the configuration from different sources.
+
+
+
+ io.helidon.config.examples.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/config/examples/sources/DirectorySourceExample.java b/examples/config/sources/src/main/java/io/helidon/config/examples/sources/DirectorySourceExample.java
new file mode 100644
index 00000000..9dbf5249
--- /dev/null
+++ b/examples/config/sources/src/main/java/io/helidon/config/examples/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.config.examples.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("changeit").asString().get();
+ System.out.println("Password: " + password);
+ assert password.equals("changeit");
+ }
+
+}
diff --git a/examples/config/sources/src/main/java/io/helidon/config/examples/sources/LoadSourcesExample.java b/examples/config/sources/src/main/java/io/helidon/config/examples/sources/LoadSourcesExample.java
new file mode 100644
index 00000000..8655c722
--- /dev/null
+++ b/examples/config/sources/src/main/java/io/helidon/config/examples/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.config.examples.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/config/examples/sources/Main.java b/examples/config/sources/src/main/java/io/helidon/config/examples/sources/Main.java
new file mode 100644
index 00000000..d6321102
--- /dev/null
+++ b/examples/config/sources/src/main/java/io/helidon/config/examples/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.config.examples.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/config/examples/sources/WithSourcesExample.java b/examples/config/sources/src/main/java/io/helidon/config/examples/sources/WithSourcesExample.java
new file mode 100644
index 00000000..5508b492
--- /dev/null
+++ b/examples/config/sources/src/main/java/io/helidon/config/examples/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.config.examples.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/config/examples/sources/package-info.java b/examples/config/sources/src/main/java/io/helidon/config/examples/sources/package-info.java
new file mode 100644
index 00000000..16f4904f
--- /dev/null
+++ b/examples/config/sources/src/main/java/io/helidon/config/examples/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.config.examples.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 00000000..b1dfac98
--- /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 00000000..721cc5fc
--- /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 00000000..c0b1365f
--- /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 00000000..a2046d7a
--- /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 00000000..e03d005c
--- /dev/null
+++ b/examples/cors/README.md
@@ -0,0 +1,173 @@
+
+# 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
+#Output: {"message":"Hello World!"}
+
+curl -X GET http://localhost:8080/greet/Joe
+#Output: {"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
+#Output: {"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: 26
+
+{"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
+#Output: {"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
+Access-Control-Allow-Methods: PUT
+Access-Control-Allow-Origin: http://foo.com
+Date: Thu, 30 Apr 2020 17:30:59 -0500
+transfer-encoding: chunked
+connection: keep-alive
+```
+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
+Access-Control-Allow-Origin: http://foo.com
+Date: Thu, 30 Apr 2020 17:32:55 -0500
+Vary: Origin
+connection: keep-alive
+```
+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
+#Output: {"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: Mon, 4 May 2020 10:49:41 -0500
+transfer-encoding: chunked
+connection: keep-alive
+```
+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
+Access-Control-Allow-Methods: PUT
+Access-Control-Allow-Origin: http://other.com
+Access-Control-Max-Age: 3600
+Date: Mon, 4 May 2020 18:52:54 -0500
+transfer-encoding: chunked
+connection: keep-alive
+```
+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.
\ No newline at end of file
diff --git a/examples/cors/pom.xml b/examples/cors/pom.xml
new file mode 100644
index 00000000..c6fba24f
--- /dev/null
+++ b/examples/cors/pom.xml
@@ -0,0 +1,97 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 2.6.8-SNAPSHOT
+
+
+ io.helidon.examples
+ helidon-examples-cors
+ 1.0.0-SNAPSHOT
+ Helidon SE Examples CORS
+
+
+ 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.config
+ helidon-config-yaml
+
+
+ io.helidon.webserver
+ helidon-webserver-cors
+
+
+ io.helidon.metrics
+ helidon-metrics-service-api
+
+
+ io.helidon.health
+ helidon-health-checks
+
+
+ io.helidon.metrics
+ helidon-metrics
+ runtime
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.webclient
+ helidon-webclient
+ 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 00000000..08d0bc55
--- /dev/null
+++ b/examples/cors/src/main/java/io/helidon/examples/cors/GreetService.java
@@ -0,0 +1,125 @@
+/*
+ * 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 javax.json.Json;
+import javax.json.JsonBuilderFactory;
+import javax.json.JsonObject;
+import javax.json.JsonReaderFactory;
+
+import io.helidon.common.http.Http;
+import io.helidon.config.Config;
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.ServerRequest;
+import io.helidon.webserver.ServerResponse;
+import io.helidon.webserver.Service;
+
+/**
+ * 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 Service {
+
+ /**
+ * The config value for the key {@code greeting}.
+ */
+ private String greeting;
+
+ private static final JsonBuilderFactory JSON_BF = Json.createBuilderFactory(Collections.emptyMap());
+
+ private static final JsonReaderFactory JSON_RF = Json.createReaderFactory(Collections.emptyMap());
+
+ GreetService(Config config) {
+ 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 update(Routing.Rules 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().param("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(Http.Status.BAD_REQUEST_400)
+ .send(jsonErrorObject);
+ return;
+ }
+
+ greeting = GreetingMessage.fromRest(jo).getMessage();
+ response.status(Http.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) {
+ request.content().as(JsonObject.class).thenAccept(jo -> 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 00000000..ac687c37
--- /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 javax.json.Json;
+import javax.json.JsonBuilderFactory;
+import javax.json.JsonObject;
+import javax.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 00000000..599da87b
--- /dev/null
+++ b/examples/cors/src/main/java/io/helidon/examples/cors/Main.java
@@ -0,0 +1,138 @@
+/*
+ * 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.io.IOException;
+import java.util.logging.Logger;
+
+import io.helidon.common.LogConfig;
+import io.helidon.common.reactive.Single;
+import io.helidon.config.Config;
+import io.helidon.health.HealthSupport;
+import io.helidon.health.checks.HealthChecks;
+import io.helidon.media.jsonp.JsonpSupport;
+import io.helidon.metrics.serviceapi.MetricsSupport;
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.cors.CorsSupport;
+import io.helidon.webserver.cors.CrossOriginConfig;
+
+/**
+ * Simple Hello World rest application.
+ */
+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 Single startServer() throws IOException {
+
+ // 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
+ Single server = WebServer.builder(createRouting(config))
+ .config(config.get("server"))
+ .addMediaSupport(JsonpSupport.create())
+ .build()
+ .start();
+
+ server.thenAccept(ws -> {
+ System.out.println(
+ "WEB server is up! http://localhost:" + ws.port() + "/greet");
+ ws.whenShutdown().thenRun(()
+ -> System.out.println("WEB server is DOWN. Good bye!"));
+ })
+ .exceptionally(t -> {
+ System.err.println("Startup failed: " + t.getMessage());
+ t.printStackTrace(System.err);
+ return null;
+ });
+
+ return server;
+ }
+
+ /**
+ * Creates new {@link Routing}.
+ *
+ * @return routing configured with JSON support, a health check, and a service
+ * @param config configuration of this server
+ */
+ private static Routing createRouting(Config config) {
+
+ MetricsSupport metrics = MetricsSupport.create();
+ GreetService greetService = new GreetService(config);
+ HealthSupport health = HealthSupport.builder()
+ .addLiveness(HealthChecks.healthChecks()) // Adds a convenient set of checks
+ .build();
+
+ // Note: Add the CORS routing *before* registering the GreetService routing.
+ return Routing.builder()
+ .register(health) // Health at "/health"
+ .register(metrics) // Metrics at "/metrics"
+ .register("/greet", corsSupportForGreeting(config), greetService)
+ .build();
+ }
+
+ private static CorsSupport corsSupportForGreeting(Config config) {
+
+ // 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 00000000..46b0ef2c
--- /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 00000000..7e04d640
--- /dev/null
+++ b/examples/cors/src/main/resources/META-INF/openapi.yml
@@ -0,0 +1,83 @@
+#
+# 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/GreetingUpdateMessage'
+ examples:
+ greeting:
+ summary: Example greeting message to update
+ value: {"greeting": "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
+ GreetingUpdateMessage:
+ properties:
+ greeting:
+ 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 00000000..dc4b4102
--- /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 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 00000000..57b07980
--- /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.common.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 00000000..62e4f0e2
--- /dev/null
+++ b/examples/cors/src/test/java/io/helidon/examples/cors/MainTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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 java.util.concurrent.TimeUnit;
+
+import javax.json.JsonObject;
+
+import io.helidon.common.http.Headers;
+import io.helidon.common.http.MediaType;
+import io.helidon.config.Config;
+import io.helidon.media.jsonp.JsonpSupport;
+import io.helidon.webclient.WebClient;
+import io.helidon.webclient.WebClientRequestBuilder;
+import io.helidon.webclient.WebClientResponse;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.cors.CrossOriginConfig;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+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.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;
+
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class MainTest {
+
+ private static WebServer webServer;
+ private static WebClient webClient;
+
+ @BeforeAll
+ public static void start() throws Exception {
+ // the port is only available if the server started already!
+ // so we need to wait
+ webServer = Main.startServer().await();
+
+ webClient = WebClient.builder()
+ .baseUri("http://localhost:" + webServer.port())
+ .addMediaSupport(JsonpSupport.create())
+ .build();
+
+ long timeout = 2000; // 2 seconds should be enough to start the server
+ long now = System.currentTimeMillis();
+
+ while (!webServer.isRunning()) {
+ Thread.sleep(100);
+ if ((System.currentTimeMillis() - now) > timeout) {
+ Assertions.fail("Failed to start webserver");
+ }
+ }
+ }
+
+ @AfterAll
+ public static void stop() {
+ if (webServer != null) {
+ webServer.shutdown()
+ .await(10, TimeUnit.SECONDS);
+ }
+ }
+
+ @Order(1) // Make sure this runs before the greeting message changes so responses are deterministic.
+ @Test
+ public void testHelloWorld() {
+
+ WebClientResponse r = getResponse("/greet");
+
+ assertThat("HTTP response1", r.status().code(), is(200));
+ assertThat("default message", fromPayload(r).getMessage(),
+ is("Hello World!"));
+
+ r = getResponse("/greet/Joe");
+ assertThat("HTTP response2", r.status().code(), is(200));
+ assertThat("hello Joe message", fromPayload(r).getMessage(),
+ is("Hello Joe!"));
+
+ r = putResponse("/greet/greeting", new GreetingMessage("Hola"));
+ assertThat("HTTP response3", r.status().code(), is(204));
+
+ r = getResponse("/greet/Jose");
+ assertThat( "HTTP response4", r.status().code(), is(200));
+ assertThat("hola Jose message", fromPayload(r).getMessage(),
+ is("Hola Jose!"));
+
+ r = getResponse("/health");
+ assertThat("HTTP response2", r.status().code(), is(200));
+
+ r = getResponse("/metrics");
+ assertThat( "HTTP response2", r.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() {
+ WebClientRequestBuilder builder = webClient.get();
+ Headers headers = builder.headers();
+ headers.add("Origin", "http://foo.com");
+ headers.add("Host", "here.com");
+
+ WebClientResponse r = getResponse("/greet", builder);
+ assertThat("HTTP response", r.status().code(), is(200));
+ String payload = fromPayload(r).getMessage();
+ assertThat("HTTP response payload was " + payload, payload, containsString("Hola World"));
+ headers = r.headers();
+ Optional allowOrigin = headers.value(CrossOriginConfig.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.
+
+ WebClientRequestBuilder builder = webClient.options();
+ Headers headers = builder.headers();
+ headers.add("Origin", "http://foo.com");
+ headers.add("Host", "here.com");
+ headers.add("Access-Control-Request-Method", "PUT");
+
+ WebClientResponse r = builder.path("/greet/greeting")
+ .submit()
+ .await();
+
+ Headers preflightResponseHeaders = r.headers();
+ List allowMethods = preflightResponseHeaders.values(CrossOriginConfig.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 = preflightResponseHeaders.values(CrossOriginConfig.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.
+
+ builder = webClient.put();
+ headers = builder.headers();
+ headers.add("Origin", "http://foo.com");
+ headers.add("Host", "here.com");
+ headers.addAll(preflightResponseHeaders);
+
+ r = putResponse("/greet/greeting", new GreetingMessage("Cheers"), builder);
+ assertThat("HTTP response3", r.status().code(), is(204));
+ headers = r.headers();
+ allowOrigins = headers.values(CrossOriginConfig.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() {
+ WebClientRequestBuilder builder = webClient.get();
+ Headers headers = builder.headers();
+ headers.add("Origin", "http://foo.com");
+ headers.add("Host", "here.com");
+
+ WebClientResponse r = getResponse("/greet/Maria", builder);
+ assertThat("HTTP response", r.status().code(), is(200));
+ assertThat(fromPayload(r).getMessage(), containsString("Cheers Maria"));
+ headers = r.headers();
+ Optional allowOrigin = headers.value(CrossOriginConfig.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() {
+ WebClientRequestBuilder builder = webClient.put();
+ Headers headers = builder.headers();
+ headers.add("Origin", "http://other.com");
+ headers.add("Host", "here.com");
+
+ WebClientResponse r = putResponse("/greet/greeting", new GreetingMessage("Ahoy"), builder);
+ // Result depends on whether we are using overrides or not.
+ boolean isOverriding = Config.create().get("cors").exists();
+ assertThat("HTTP response3", r.status().code(), is(isOverriding ? 204 : 403));
+ }
+
+ private static WebClientResponse getResponse(String path) {
+ return getResponse(path, webClient.get());
+ }
+
+ private static WebClientResponse getResponse(String path, WebClientRequestBuilder builder) {
+ return builder
+ .accept(MediaType.APPLICATION_JSON)
+ .path(path)
+ .submit()
+ .await();
+ }
+
+ private static WebClientResponse putResponse(String path, GreetingMessage payload) {
+ return putResponse(path, payload, webClient.put());
+ }
+
+ private static WebClientResponse putResponse(String path, GreetingMessage payload, WebClientRequestBuilder builder) {
+ return builder
+ .accept(MediaType.APPLICATION_JSON)
+ .path(path)
+ .submit(payload.forRest())
+ .await();
+ }
+
+ private static GreetingMessage fromPayload(WebClientResponse response) {
+ JsonObject json = response
+ .content()
+ .as(JsonObject.class)
+ .await();
+
+ return GreetingMessage.fromRest(json);
+ }
+}
diff --git a/examples/dbclient/README.md b/examples/dbclient/README.md
new file mode 100644
index 00000000..8dbf2d25
--- /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 00000000..bc79a50a
--- /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 00000000..ef7242a7
--- /dev/null
+++ b/examples/dbclient/common/pom.xml
@@ -0,0 +1,45 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples.dbclient
+ helidon-examples-dbclient-project
+ 1.0.0-SNAPSHOT
+
+
+ helidon-examples-dbclient-common
+ Helidon Examples DB Client Common
+
+
+
+ io.helidon.dbclient
+ helidon-dbclient
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.media
+ helidon-media-jsonp
+
+
+
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 00000000..cbd7fffb
--- /dev/null
+++ b/examples/dbclient/common/src/main/java/io/helidon/examples/dbclient/common/AbstractPokemonService.java
@@ -0,0 +1,240 @@
+/*
+ * 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.concurrent.CompletionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.json.JsonObject;
+
+import io.helidon.common.http.Http;
+import io.helidon.common.reactive.Multi;
+import io.helidon.common.reactive.Single;
+import io.helidon.dbclient.DbClient;
+import io.helidon.dbclient.DbRow;
+import io.helidon.webserver.Handler;
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.ServerRequest;
+import io.helidon.webserver.ServerResponse;
+import io.helidon.webserver.Service;
+
+/**
+ * Common methods that do not differ between JDBC and MongoDB.
+ */
+public abstract class AbstractPokemonService implements Service {
+ private static final Logger LOGGER = Logger.getLogger(AbstractPokemonService.class.getName());
+
+ private final DbClient dbClient;
+
+ /**
+ * Create a new pokemon service with a DB client.
+ *
+ * @param dbClient DB client to use for database operations
+ */
+ protected AbstractPokemonService(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ @Override
+ public void update(Routing.Rules 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", Handler.create(Pokemon.class, 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 request Server request
+ * @param response Server response
+ */
+ protected abstract void deleteAllPokemons(ServerRequest request, ServerResponse response);
+
+ /**
+ * Insert new pokemon with specified name.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void insertPokemon(ServerRequest request, ServerResponse response, Pokemon pokemon) {
+ dbClient.execute(exec -> exec
+ .createNamedInsert("insert2")
+ .namedParam(pokemon)
+ .execute())
+ .thenAccept(count -> response.send("Inserted: " + count + " values"))
+ .exceptionally(throwable -> sendError(throwable, response));
+ }
+
+ /**
+ * Insert new pokemon with specified name.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void insertPokemonSimple(ServerRequest request, ServerResponse response) {
+ // Test Pokemon POJO mapper
+ Pokemon pokemon = new Pokemon(request.path().param("name"), request.path().param("type"));
+
+ dbClient.execute(exec -> exec
+ .createNamedInsert("insert2")
+ .namedParam(pokemon)
+ .execute())
+ .thenAccept(count -> response.send("Inserted: " + count + " values"))
+ .exceptionally(throwable -> sendError(throwable, response));
+ }
+
+ /**
+ * Get a single pokemon by name.
+ *
+ * @param request server request
+ * @param response server response
+ */
+ private void getPokemon(ServerRequest request, ServerResponse response) {
+ String pokemonName = request.path().param("name");
+
+ dbClient.execute(exec -> exec.namedGet("select-one", pokemonName))
+ .thenAccept(opt -> opt.ifPresentOrElse(it -> sendRow(it, response),
+ () -> sendNotFound(response, "Pokemon "
+ + pokemonName
+ + " not found")))
+ .exceptionally(throwable -> sendError(throwable, response));
+ }
+
+ /**
+ * Return JsonArray with all stored pokemons or pokemons with matching attributes.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void listPokemons(ServerRequest request, ServerResponse response) {
+ Multi rows = dbClient.execute(exec -> exec.namedQuery("select-all"))
+ .map(it -> it.as(JsonObject.class));
+
+ response.send(rows, JsonObject.class);
+ }
+
+ /**
+ * Update a pokemon.
+ * Uses a transaction.
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void updatePokemonType(ServerRequest request, ServerResponse response) {
+ final String name = request.path().param("name");
+ final String type = request.path().param("type");
+
+ dbClient.execute(exec -> exec
+ .createNamedUpdate("update")
+ .addParam("name", name)
+ .addParam("type", type)
+ .execute())
+ .thenAccept(count -> response.send("Updated: " + count + " values"))
+ .exceptionally(throwable -> sendError(throwable, response));
+ }
+
+ private void transactional(ServerRequest request, ServerResponse response, Pokemon pokemon) {
+
+ dbClient.inTransaction(tx -> tx
+ .createNamedGet("select-for-update")
+ .namedParam(pokemon)
+ .execute()
+ .flatMapSingle(maybeRow -> maybeRow.map(dbRow -> tx.createNamedUpdate("update")
+ .namedParam(pokemon).execute())
+ .orElseGet(() -> Single.just(0L)))
+ ).thenAccept(count -> response.send("Updated " + count + " records"));
+
+ }
+
+ /**
+ * Delete pokemon with specified name (key).
+ *
+ * @param request the server request
+ * @param response the server response
+ */
+ private void deletePokemon(ServerRequest request, ServerResponse response) {
+ final String name = request.path().param("name");
+
+ dbClient.execute(exec -> exec.namedDelete("delete", name))
+ .thenAccept(count -> response.send("Deleted: " + count + " values"))
+ .exceptionally(throwable -> sendError(throwable, response));
+ }
+
+ /**
+ * Send a 404 status code.
+ *
+ * @param response the server response
+ * @param message entity content
+ */
+ protected void sendNotFound(ServerResponse response, String message) {
+ response.status(Http.Status.NOT_FOUND_404);
+ response.send(message);
+ }
+
+ /**
+ * Send a single DB row as JSON object.
+ *
+ * @param row row as read from the database
+ * @param response server response
+ */
+ protected void sendRow(DbRow row, ServerResponse response) {
+ response.send(row.as(JsonObject.class));
+ }
+
+ /**
+ * Send a 500 response code and a few details.
+ *
+ * @param throwable throwable that caused the issue
+ * @param response server response
+ * @param type of expected response, will be always {@code null}
+ * @return {@code Void} so this method can be registered as a lambda
+ * with {@link java.util.concurrent.CompletionStage#exceptionally(java.util.function.Function)}
+ */
+ protected T sendError(Throwable throwable, ServerResponse response) {
+ Throwable realCause = throwable;
+ if (throwable instanceof CompletionException) {
+ realCause = throwable.getCause();
+ }
+ response.status(Http.Status.INTERNAL_SERVER_ERROR_500);
+ response.send("Failed to process request: " + realCause.getClass().getName() + "(" + realCause.getMessage() + ")");
+ LOGGER.log(Level.WARNING, "Failed to process request", throwable);
+ return null;
+ }
+
+}
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 00000000..39382258
--- /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 Pokemon.
+ */
+@Reflected
+public class Pokemon {
+ private String name;
+ private String type;
+
+ /**
+ * Default constructor.
+ */
+ public Pokemon() {
+ // JSON-B
+ }
+
+ /**
+ * Create pokemon 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 00000000..9ac55a12
--- /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.as(String.class), type.as(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