diff --git a/.gitignore b/.gitignore index b1bf6a636d4..6918d53ea85 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,5 @@ docs/se/config/images # Baseline results for regression tests jmh-baseline.json + +.helidon-oidc-secret diff --git a/THIRD_PARTY_LICENSES.txt b/THIRD_PARTY_LICENSES.txt index 368dd3fabc5..7f6f1997770 100644 --- a/THIRD_PARTY_LICENSES.txt +++ b/THIRD_PARTY_LICENSES.txt @@ -31,7 +31,7 @@ Fourth Party Runtime Dependencies =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= CDI-API RedHat, Inc., JBoss community Apache 2.0 -Used by: [helidon-health-checks, helidon-integrations-cdi-datasource, helidon-integrations-cdi-datasource-hikaricp, helidon-integrations-cdi-datasource-ucp, helidon-integrations-cdi-delegates, helidon-integrations-cdi-eclipselink, helidon-integrations-cdi-hibernate, helidon-integrations-cdi-jpa, helidon-integrations-cdi-jta, helidon-integrations-cdi-jta-weld, helidon-integrations-cdi-reference-counted-context, helidon-integrations-micrometer-cdi, helidon-integrations-micronaut-cdi, helidon-integrations-micronaut-data, helidon-integrations-microstream-cdi, helidon-integrations-oci-sdk-cdi, helidon-integrations-vault-cdi, helidon-jersey-client, helidon-messaging-aq, helidon-messaging-jms, helidon-messaging-kafka, helidon-messaging-mock, helidon-messaging-wls-jms, helidon-microprofile-config, helidon-microprofile-cors, helidon-microprofile-fault-tolerance, helidon-microprofile-metrics, helidon-microprofile-server, helidon-microprofile-service-common, helidon-microprofile-telemetry, helidon-microprofile-tracing, helidon-microprofile-websocket, weld-se-core] +Used by: [helidon-health-checks, helidon-integrations-cdi-datasource, helidon-integrations-cdi-datasource-hikaricp, helidon-integrations-cdi-datasource-ucp, helidon-integrations-cdi-delegates, helidon-integrations-cdi-eclipselink, helidon-integrations-cdi-hibernate, helidon-integrations-cdi-jpa, helidon-integrations-cdi-jta, helidon-integrations-cdi-jta-weld, helidon-integrations-cdi-reference-counted-context, helidon-integrations-micrometer-cdi, helidon-integrations-micronaut-cdi, helidon-integrations-micronaut-data, helidon-integrations-microstream-cdi, helidon-integrations-eclipsestore-cdi, helidon-integrations-oci-sdk-cdi, helidon-integrations-vault-cdi, helidon-jersey-client, helidon-messaging-aq, helidon-messaging-jms, helidon-messaging-kafka, helidon-messaging-mock, helidon-messaging-wls-jms, helidon-microprofile-config, helidon-microprofile-cors, helidon-microprofile-fault-tolerance, helidon-microprofile-metrics, helidon-microprofile-server, helidon-microprofile-service-common, helidon-microprofile-telemetry, helidon-microprofile-tracing, helidon-microprofile-websocket, weld-se-core] =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Jakarta CDI API (jakarta.enterprise:jakarta.enterprise.cdi-api) Copyright 2010, 2016, Red Hat, Inc., and individual contributors @@ -1069,7 +1069,7 @@ See full text at the bottom of this document for license: Apache-2.0 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Jakarta Annotations API Eclipse Foundation Eclipse Public License 2.0 + GPL v.2 with CPE -Used by: [helidon-bundles-config, helidon-config, helidon-config-hocon, helidon-config-mp, helidon-config-tests-module-mappers-1-base, helidon-config-tests-module-mappers-2-override, helidon-config-tests-module-parsers-1-override, helidon-config-tests-test-bundle, helidon-config-yaml, helidon-config-yaml-mp, helidon-http-encoding, helidon-inject-api, helidon-inject-configdriven-runtime, helidon-inject-configdriven-tests-config, helidon-inject-configdriven-tests-configuredby, helidon-inject-configdriven-tests-configuredby-application, helidon-inject-maven-plugin, helidon-inject-runtime, helidon-inject-tests-interception, helidon-inject-tests-resources-inject, helidon-inject-tests-resources-plain, helidon-inject-tests-tck-jsr330, helidon-inject-tools, helidon-integrations-cdi-datasource, helidon-integrations-cdi-datasource-hikaricp, helidon-integrations-cdi-datasource-ucp, helidon-integrations-cdi-jpa, helidon-integrations-cdi-jta, helidon-integrations-micronaut-cdi-processor, helidon-integrations-micronaut-data, helidon-integrations-microstream-cdi, helidon-integrations-oci-sdk-cdi, helidon-integrations-oci-sdk-tests-test-application, helidon-integrations-oci-sdk-tests-test-module1, helidon-integrations-oci-sdk-tests-test-module2, helidon-integrations-oci-tls-certificates, helidon-jersey-client, helidon-messaging-mock, helidon-microprofile-fault-tolerance, helidon-microprofile-telemetry, weld-se-core] +Used by: [helidon-bundles-config, helidon-config, helidon-config-hocon, helidon-config-mp, helidon-config-tests-module-mappers-1-base, helidon-config-tests-module-mappers-2-override, helidon-config-tests-module-parsers-1-override, helidon-config-tests-test-bundle, helidon-config-yaml, helidon-config-yaml-mp, helidon-http-encoding, helidon-inject-api, helidon-inject-configdriven-runtime, helidon-inject-configdriven-tests-config, helidon-inject-configdriven-tests-configuredby, helidon-inject-configdriven-tests-configuredby-application, helidon-inject-maven-plugin, helidon-inject-runtime, helidon-inject-tests-interception, helidon-inject-tests-resources-inject, helidon-inject-tests-resources-plain, helidon-inject-tests-tck-jsr330, helidon-inject-tools, helidon-integrations-cdi-datasource, helidon-integrations-cdi-datasource-hikaricp, helidon-integrations-cdi-datasource-ucp, helidon-integrations-cdi-jpa, helidon-integrations-cdi-jta, helidon-integrations-micronaut-cdi-processor, helidon-integrations-micronaut-data, helidon-integrations-microstream-cdi, helidon-integrations-eclipsestore-cdi, helidon-integrations-oci-sdk-cdi, helidon-integrations-oci-sdk-tests-test-application, helidon-integrations-oci-sdk-tests-test-module1, helidon-integrations-oci-sdk-tests-test-module2, helidon-integrations-oci-tls-certificates, helidon-jersey-client, helidon-messaging-mock, helidon-microprofile-fault-tolerance, helidon-microprofile-telemetry, weld-se-core] =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api) Copyright (c) 2009, 2022 Oracle and/or its affiliates. All rights reserved. @@ -8305,6 +8305,64 @@ Fourth Party Runtime Dependencies EPL-2.0 ----------------------------------------- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +eclipsestore-storage-embedded-configuration Microstream Software +Eclipse Public License 2.0 +Used by: [helidon-integrations-eclipsestore] +=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +1. The following files are available in source code form under the Eclipse Public License at: + https://github.com/eclipsestore/store/ + (The EPL license is reproduced below). +2. All past Contributors to the Eclipsestore project disclaim all warranties and + conditions, express and implied, including warranties or conditions of title and + non-infringement, and implied warranties or conditions of merchantability and + fitness for a particular purpose. In addition, such Contributors are not liable + for any damages, including direct, indirect, special, incidental and consequential + damages, such as lost profits. +3. Any provisions of the Oracle license agreement that differ from the Eclipse Public + License are offered by Oracle alone and not by any other party. +_________________________________________________________ + +Eclipsestore Embedded Storage Configuration (org.eclipse.store:eclipsestore-storage-embedded-configuration) + Copyright (C) 2024 MicroStream Software +-------------------------------------------- +See full text at the bottom of this document for license: EPL-2.0 +-------------------------------------------- +Fourth Party Runtime Dependencies +-------------------------------------------- +"Eclipsestore Embedded Storage" (org.eclipse.store:eclipsestore-storage-embedded) + Copyright (C) 2024 MicroStream Software + EPL-2.0 +-------------------------------------------- +"Eclipsestore Storage" (org.eclipse.store:eclipsestore-storage) + Copyright (C) 2024 MicroStream Software + EPL-2.0 +-------------------------------------------- +"Eclipsestore Abstract File System - Java NIO" (org.eclipse.store:eclipsestore-afs-nio) + Copyright (C) 2024 MicroStream Software + EPL-2.0 +-------------------------------------------- +"Eclipsestore Abstract File System" (org.eclipse.store:eclipsestore-afs) + Copyright (C) 2024 MicroStream Software + EPL-2.0 +-------------------------------------------- +"Eclipsestore Persistence Binary" (org.eclipse.store:eclipsestore-persistence-binary) + Copyright (C) 2024 MicroStream Software + EPL-2.0 +-------------------------------------------- +"Eclipsestore Persistence" (org.eclipse.store:eclipsestore-persistence) + Copyright (C) 2024 MicroStream Software + EPL-2.0 +-------------------------------------------- +"Eclipsestore Configuration" (org.eclipse.store:eclipsestore-configuration) + Copyright (C) 2024 MicroStream Software + EPL-2.0 +-------------------------------------------- +"Eclipsestore Base" (org.eclipse.store:eclipsestore-base) + Copyright (C) 2024 MicroStream Software + EPL-2.0 +--------------------------------- +=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= microstream-storage-embedded-configuration Microstream Software Eclipse Public License 2.0 Used by: [helidon-integrations-microstream] diff --git a/all/pom.xml b/all/pom.xml index f97cd9e2c7c..db5b08fbdc9 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -826,6 +826,28 @@ helidon-integrations-microstream-cache + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-cdi + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-health + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-metrics + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-cache + + + io.helidon.http helidon-http diff --git a/bom/pom.xml b/bom/pom.xml index 2715a75f343..02778103289 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -1091,7 +1091,32 @@ helidon-integrations-microstream-cache ${helidon.version} - + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore + ${helidon.version} + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-cdi + ${helidon.version} + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-health + ${helidon.version} + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-metrics + ${helidon.version} + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-cache + ${helidon.version} + io.helidon.http helidon-http diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 4a7d3ac316e..f43cb8f0904 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -119,7 +119,9 @@ 1.0 3.0 2.0 - 08.01.01-MS-GA + 08.01.02-MS-GA + 1.2.0 + 1.2.0 4.10.2 8.4.1.jre8 8.2.0 @@ -796,6 +798,29 @@ ${version.lib.microstream} + + + org.eclipse.store + storage-embedded + ${version.lib.eclipse-store} + + + org.eclipse.store + storage-embedded-configuration + ${version.lib.eclipse-store} + + + org.eclipse.store + cache + ${version.lib.eclipse-store} + + + org.eclipse.serializer + persistence-binary-jdk17 + ${version.lib.eclipse-serializer} + + + jakarta.persistence diff --git a/examples/integrations/eclipsestore/README.md b/examples/integrations/eclipsestore/README.md new file mode 100644 index 00000000000..48dd21a24a8 --- /dev/null +++ b/examples/integrations/eclipsestore/README.md @@ -0,0 +1 @@ +# EclipseStore Integrations Examples diff --git a/examples/integrations/eclipsestore/greetings-mp/README.md b/examples/integrations/eclipsestore/greetings-mp/README.md new file mode 100644 index 00000000000..68f6f047f55 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-mp/README.md @@ -0,0 +1,28 @@ +# EclipseStore integration example + +This example uses EclipseStore to persist the greetings supplied + +## Build and run + +```shell +mvn package +java -jar target/helidon-examples-integrations-eclipsestore-greetings-mp.jar +``` + +## Endpoints + +Get default greeting message: +```shell +curl -X GET http://localhost:7001/greet +``` + +Get greeting message for Joe: + +```shell +curl -X GET http://localhost:7001/greet/Joe +``` + +Add a greeting: +```shell +curl -X PUT -H "Content-Type: application/json" -d '{"message" : "Howdy"}' http://localhost:7001/greet/greeting +``` diff --git a/examples/integrations/eclipsestore/greetings-mp/pom.xml b/examples/integrations/eclipsestore/greetings-mp/pom.xml new file mode 100644 index 00000000000..228cb4ab280 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-mp/pom.xml @@ -0,0 +1,84 @@ + + + + 4.0.0 + + io.helidon.applications + helidon-mp + 4.0.0-SNAPSHOT + ../../../../applications/mp/pom.xml + + + helidon-examples-integrations-eclipsestore-greetings-mp + Helidon Examples Integration EclipseStore Greetings mp + + + + io.helidon.microprofile.bundles + helidon-microprofile + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-cdi + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + io.helidon.microprofile.testing + helidon-microprofile-testing-junit5 + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + io.smallrye + jandex-maven-plugin + + + make-index + + + + + + + diff --git a/examples/integrations/eclipsestore/greetings-mp/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/GreetResource.java b/examples/integrations/eclipsestore/greetings-mp/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/GreetResource.java new file mode 100644 index 00000000000..bfc43385e7d --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-mp/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/GreetResource.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.integrations.eclipsestore.greetings.mp; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +/** + * A simple service to greet you. Examples: + * + * Get default greeting message: + * curl -X GET http://localhost:7001/greet + * + * Get greeting message for Joe: + * curl -X GET http://localhost:7001/greet/Joe + * + * add a greeting: + * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' + * http://localhost:7001/greet/greeting + * + * The message is returned as a JSON object + */ +@Path("/greet") +@RequestScoped +public class GreetResource { + + /** + * Greeting provider. + */ + private final GreetingProvider greetingProvider; + + /** + * Using constructor injection to get a configuration property. + * By default this gets the value from META-INF/microprofile-config + * + * @param provider the configured greeting message + */ + @Inject + public GreetResource(final GreetingProvider provider) { + this.greetingProvider = provider; + } + + /** + * Return a default greeting message. + * + * @return {@link GreetingMessage} + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + public GreetingMessage getDefaultMessage() { + return createResponse("World"); + } + + private GreetingMessage createResponse(final String who) { + String msg = String + .format("%s %s!", greetingProvider.getGreeting(), who); + return new GreetingMessage(msg); + } + + /** + * Return a greeting message using the name that was provided. + * + * @param name the name to greet + * @return {@link GreetingMessage} + */ + @Path("/{name}") + @GET + @Produces(MediaType.APPLICATION_JSON) + public GreetingMessage getMessage(@PathParam("name") final String name) { + return createResponse(name); + } + + /** + * Set the greeting to use in future messages. + * + * @param message JSON containing the new greeting + * @return {@link Response} + */ + @Path("/greeting") + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response updateGreeting(final GreetingMessage message) { + if (message.getMessage() == null) { + GreetingMessage entity = + new GreetingMessage("No greeting provided"); + + return Response + .status(Response.Status.BAD_REQUEST) + .entity(entity).build(); + } + + greetingProvider.addGreeting(message.getMessage()); + return Response.status(Response.Status.NO_CONTENT).build(); + } + +} diff --git a/examples/integrations/eclipsestore/greetings-mp/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/GreetingMessage.java b/examples/integrations/eclipsestore/greetings-mp/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/GreetingMessage.java new file mode 100644 index 00000000000..9cb0d78fdd7 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-mp/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/GreetingMessage.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.examples.integrations.eclipsestore.greetings.mp; + +/** + * POJO defining the greeting message content. + */ +@SuppressWarnings("unused") +public class GreetingMessage { + /** + * Message. + */ + private String theMessage; + + /** + * Create a new GreetingMessage instance. + */ + public GreetingMessage() { + } + + /** + * Create a new GreetingMessage instance. + * + * @param message message + */ + public GreetingMessage(final String message) { + this.theMessage = message; + } + + /** + * Gets the message value. + * + * @return message value + */ + public String getMessage() { + return theMessage; + } + + /** + * Sets the message value. + * + * @param message message value to set + */ + public void setMessage(final String message) { + this.theMessage = message; + } +} diff --git a/examples/integrations/eclipsestore/greetings-mp/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/GreetingProvider.java b/examples/integrations/eclipsestore/greetings-mp/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/GreetingProvider.java new file mode 100644 index 00000000000..dc192fe1ec6 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-mp/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/GreetingProvider.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.integrations.eclipsestore.greetings.mp; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import io.helidon.integrations.eclipsestore.cdi.EclipseStoreStorage; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; + +/** + * Provider for greeting message that are persisted by eclipsestore. + */ +@ApplicationScoped +public class GreetingProvider { + + /** + * A lock. + */ + private final ReentrantReadWriteLock lock + = new ReentrantReadWriteLock(true); + + /** + * Eclipse Storage manager. + */ + private final EmbeddedStorageManager storage; + + /** + * Random numbers generator. + */ + private final Random rnd = new Random(); + + /** + * Gertting messages. + */ + private List greetingMessages; + + /** + * Creates new GreetingProvider using a eclipsestore EmbeddedStorageManager. + * + * @param eclipseStorageManager the used EmbeddedStorageManager. + */ + @SuppressWarnings("unchecked") + @Inject + public GreetingProvider( + @EclipseStoreStorage(configNode = "one.eclipsestore.storage.greetings") + final EmbeddedStorageManager eclipseStorageManager) { + super(); + storage = eclipseStorageManager; + + // load stored data + greetingMessages = (List) storage.root(); + + // Initialize storage if empty + if (greetingMessages == null) { + greetingMessages = new ArrayList<>(); + storage.setRoot(greetingMessages); + storage.storeRoot(); + addGreeting("Hello"); + } + } + + /** + * Add a new greeting to the available greetings and persist it. + * + * @param newGreeting the new greeting to be added and persisted. + */ + public void addGreeting(final String newGreeting) { + try { + lock.writeLock().lock(); + greetingMessages.add(newGreeting); + storage.store(greetingMessages); + } finally { + lock.writeLock().unlock(); + } + } + + /** + * returns a random greeting. + * + * @return a greeting. + */ + public String getGreeting() { + try { + lock.readLock().lock(); + return greetingMessages.get(rnd.nextInt(greetingMessages.size())); + } finally { + lock.readLock().unlock(); + } + } + +} diff --git a/examples/integrations/eclipsestore/greetings-mp/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/package-info.java b/examples/integrations/eclipsestore/greetings-mp/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/package-info.java new file mode 100644 index 00000000000..5a35cec77bd --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-mp/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * An example that uses EclipseStore to persist the greetings. + */ +package io.helidon.examples.integrations.eclipsestore.greetings.mp; diff --git a/examples/integrations/eclipsestore/greetings-mp/src/main/resources/META-INF/beans.xml b/examples/integrations/eclipsestore/greetings-mp/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..e149ced7d1a --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-mp/src/main/resources/META-INF/beans.xml @@ -0,0 +1,25 @@ + + + + diff --git a/examples/integrations/eclipsestore/greetings-mp/src/main/resources/META-INF/eclipsestore-config.properties b/examples/integrations/eclipsestore/greetings-mp/src/main/resources/META-INF/eclipsestore-config.properties new file mode 100644 index 00000000000..fcbcd75ab08 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-mp/src/main/resources/META-INF/eclipsestore-config.properties @@ -0,0 +1,17 @@ +# +# Copyright (c) 2019, 2024 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +org.eclipsestore.storage.greetings.storage-directory=./greetingsStorage diff --git a/examples/integrations/eclipsestore/greetings-mp/src/test/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/EclipseStoreExampleGreetingsMpTest.java b/examples/integrations/eclipsestore/greetings-mp/src/test/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/EclipseStoreExampleGreetingsMpTest.java new file mode 100644 index 00000000000..198ab3ed993 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-mp/src/test/java/io/helidon/examples/integrations/eclipsestore/greetings/mp/EclipseStoreExampleGreetingsMpTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.integrations.eclipsestore.greetings.mp; + +import java.nio.file.Path; + +import io.helidon.microprofile.testing.junit5.HelidonTest; + +import jakarta.inject.Inject; +import jakarta.ws.rs.client.WebTarget; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@HelidonTest +class EclipseStoreExampleGreetingsMpTest { + + @Inject + private WebTarget webTarget; + + @TempDir + static Path tempDir; + + @BeforeAll + static void beforeAll() { + System.setProperty("one.eclipsestore.storage.greetings.storage-directory", tempDir.toString()); + } + + @Test + void testGreeting() { + GreetingMessage response = webTarget.path("/greet").request().get(GreetingMessage.class); + + assertEquals("Hello World!", response.getMessage(), "response should be 'Hello World' "); + } + +} diff --git a/examples/integrations/eclipsestore/greetings-se/README.md b/examples/integrations/eclipsestore/greetings-se/README.md new file mode 100644 index 00000000000..585db6c8c3b --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-se/README.md @@ -0,0 +1,32 @@ +# EclipseStore integration example + +This example uses EclipseStore to persist a log entry for every greeting + +## Build and run + +```shell +mvn package +java -jar target/helidon-examples-integrations-eclipsestore-greetings-se.jar +``` + +## Endpoints + +Get default greeting message: +```shell +curl -X GET http://localhost:8080/greet +``` + +Get greeting message for Joe: +```shell +curl -X GET http://localhost:8080/greet/Joe +``` + +Change greeting: +```shell +curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting +``` + +Get the logs: +```shell +curl -X GET http://localhost:8080/greet/logs +``` diff --git a/examples/integrations/eclipsestore/greetings-se/pom.xml b/examples/integrations/eclipsestore/greetings-se/pom.xml new file mode 100644 index 00000000000..ea52ef71041 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-se/pom.xml @@ -0,0 +1,106 @@ + + + + 4.0.0 + + io.helidon.applications + helidon-se + 4.0.0-SNAPSHOT + ../../../../applications/se/pom.xml + + + helidon-examples-integrations-eclipsestore-greetings-se + Helidon Examples Integration EclipseStore Greetings se + + + io.helidon.examples.integrations.eclipsestore.greetings.se.Main + + + + + io.helidon.webserver + helidon-webserver + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore + + + io.helidon.webserver.observe + helidon-webserver-observe + + + io.helidon.http.media + helidon-http-media-jsonp + + + io.helidon.config + helidon-config-yaml + + + io.helidon.health + helidon-health-checks + + + io.helidon.metrics + helidon-metrics-api + + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + runtime + + + io.helidon.metrics + helidon-metrics-system-meters + runtime + + + io.helidon.webserver.testing.junit5 + helidon-webserver-testing-junit5 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + + diff --git a/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/EclipseStoreExecutionContext.java b/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/EclipseStoreExecutionContext.java new file mode 100644 index 00000000000..477f982e205 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/EclipseStoreExecutionContext.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.integrations.eclipsestore.greetings.se; + + +import org.eclipse.serializer.reference.LazyReferenceManager; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; + +/** + * Provides a very simply way to access a EclipseStore storage, + * and it's associated data. + */ +public class EclipseStoreExecutionContext { + + /** + * Eclipse Storage manager. + */ + private final EmbeddedStorageManager storage; + + /** + * Creates a new instance. + * + * @param storageManager the used EmbeddedStorageManager. + */ + public EclipseStoreExecutionContext( + final EmbeddedStorageManager storageManager) { + this.storage = storageManager; + } + + /** + * returns the used storageManager. + * + * @return the used EmbeddedStorageManager. + */ + public EmbeddedStorageManager storageManager() { + return storage; + } + + /** + * Start the storage. + * + * @return the started EmbeddedStorageManager. + */ + public EmbeddedStorageManager start() { + return storage.start(); + } + + /** + * Shutdown the storage. + * + * @return the stopped EmbeddedStorageManager. + */ + public EmbeddedStorageManager shutdown() { + storage.shutdown(); + LazyReferenceManager.get().stop(); + return storage; + } + + /** + * Return the persistent object graph's root object. + * + * @param type of the root object + * @return the graph's root object casted to + */ + @SuppressWarnings("unchecked") + public T root() { + return (T) storage.root(); + } + + /** + * Sets the passed instance as the new root for the persistent object graph. + * + * @param object the new root object + * @return the new root object + */ + public Object setRoot(final Object object) { + return storage.setRoot(object); + } + + /** + * Stores the registered root instance. + * + * @return the root instance's objectId. + */ + public long storeRoot() { + return storage.storeRoot(); + } + + /** + * Stores the passed object. + * + * @param object object to store + * @return the object id representing the passed instance. + */ + public long store(final Object object) { + return storage.store(object); + } +} diff --git a/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/GreetingService.java b/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/GreetingService.java new file mode 100644 index 00000000000..8bda36d76b6 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/GreetingService.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.integrations.eclipsestore.greetings.se; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicReference; + +import io.helidon.config.Config; +import io.helidon.http.Status; +import io.helidon.integrations.eclipsestore.core.EmbeddedStorageManagerBuilder; +import io.helidon.webserver.http.HttpRules; +import io.helidon.webserver.http.HttpService; +import io.helidon.webserver.http.ServerRequest; +import io.helidon.webserver.http.ServerResponse; + +import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonBuilderFactory; +import jakarta.json.JsonObject; + +/** + * A simple service to greet you. Examples: + *

+ * Get default greeting message: + * curl -X GET http://localhost:8080/greet + *

+ * Get greeting message for Joe: + * curl -X GET http://localhost:8080/greet/Joe + *

+ * Change greeting: + * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' + * http://localhost:8080/greet/greeting + *

+ * Get the logs: + * curl -X GET http://localhost:8080/greet/logs + *

+ * The message is returned as a JSON object + */ + +public final class GreetingService implements HttpService { + + /** + * Greeting reference. + */ + private final AtomicReference greeting = new AtomicReference<>(); + + /** + * Json factory. + */ + private static final JsonBuilderFactory JSON = + Json.createBuilderFactory(Collections.emptyMap()); + + /** + * Eclipse store context. + */ + private final GreetingServiceEclipseStoreContext context; + + /** + * Create greeting service. + * @param config configuration. + */ + GreetingService(final Config config) { + greeting.set( + config.get("app.greeting").asString().orElse("Ciao")); + + context = new GreetingServiceEclipseStoreContext( + EmbeddedStorageManagerBuilder.create( + config.get("eclipsestore"))); + // we need to initialize the root element first + // if we do not wait here, we have a race where HTTP method + // may be invoked before we initialize root + context.start(); + context.initRootElement(); + } + + @Override + public void routing(final HttpRules rules) { + rules.get("/", this::getDefaultMessageHandler) + .get("/logs", this::getLog) + .get("/{name}", this::getMessageHandler) + .put("/greeting", this::updateGreetingHandler); + } + + private void getLog(final ServerRequest request, + final ServerResponse response) { + JsonArrayBuilder arrayBuilder = JSON.createArrayBuilder(); + context.getLogs().forEach((entry) -> arrayBuilder.add( + JSON.createObjectBuilder() + .add("name", entry.name()) + .add("time", entry.dateTime().toString()))); + response.send(arrayBuilder.build()); + } + + /** + * Return a worldly greeting message. + * + * @param request the server request + * @param response the server response + */ + private void getDefaultMessageHandler(final ServerRequest request, + final 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(final ServerRequest request, + final ServerResponse response) { + String name = request.path().pathParameters().get("name"); + sendResponse(response, name); + } + + private void sendResponse(final ServerResponse response, + final String name) { + String msg = String.format("%s %s!", greeting.get(), name); + + context.addLogEntry(name); + + JsonObject returnObject = JSON.createObjectBuilder() + .add("message", msg) + .build(); + response.send(returnObject); + } + + private void updateGreetingFromJson(final JsonObject jo, + final ServerResponse response) { + if (!jo.containsKey("greeting")) { + JsonObject jsonErrorObject = JSON.createObjectBuilder() + .add("error", "No greeting provided") + .build(); + response.status(Status.BAD_REQUEST_400) + .send(jsonErrorObject); + return; + } + + greeting.set(jo.getString("greeting")); + response.status(Status.NO_CONTENT_204).send(); + } + + /** + * Set the greeting to use in future messages. + * + * @param request the server request + * @param response the server response + */ + private void updateGreetingHandler(final ServerRequest request, + final ServerResponse response) { + JsonObject jsonObject = request.content().as(JsonObject.class); + updateGreetingFromJson(jsonObject, response); + } + +} diff --git a/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/GreetingServiceEclipseStoreContext.java b/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/GreetingServiceEclipseStoreContext.java new file mode 100644 index 00000000000..86d16479768 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/GreetingServiceEclipseStoreContext.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.integrations.eclipsestore.greetings.se; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; + +/** + * This class extends {@link EclipseStoreExecutionContext} + * and provides data access methods. + */ +public class GreetingServiceEclipseStoreContext + extends EclipseStoreExecutionContext { + + /** + * Create a new instance. + * + * @param storageManager the EmbeddedStorageManager used. + */ + public GreetingServiceEclipseStoreContext( + final EmbeddedStorageManager storageManager) { + super(storageManager); + } + + /** + * Add and store a new log entry. + * + * @param name parameter for log text. + */ + @SuppressWarnings({"unchecked", "resource"}) + public void addLogEntry(final String name) { + List logs = (List) storageManager().root(); + logs.add(new LogEntry(name, LocalDateTime.now())); + storageManager().store(logs); + } + + /** + * initialize the storage root with a new, empty List. + */ + @SuppressWarnings("resource") + public void initRootElement() { + if (storageManager().root() == null) { + storageManager().setRoot(new ArrayList()); + storageManager().storeRoot(); + } + } + + /** + * returns a List of all stored LogEntries. + * + * @return all LogEntries. + */ + @SuppressWarnings({"unchecked", "resource"}) + public List getLogs() { + return (List) storageManager().root(); + } + +} diff --git a/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/LogEntry.java b/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/LogEntry.java new file mode 100644 index 00000000000..3012a8e5c66 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/LogEntry.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.integrations.eclipsestore.greetings.se; + +import java.time.LocalDateTime; + + +/** + * Simple POJO that represents a Log entry that is stored by + * Eclipse store in this example. + */ +public final class LogEntry { + + /** + * Name to be logged. + */ + private final String name; + + /** + * Date and time to be logged. + */ + private final LocalDateTime dateTime; + + /** + * @param aName name to be logged. + * @param aDateTime dateTime date and time to be logged + */ + public LogEntry(final String aName, final LocalDateTime aDateTime) { + this.name = aName; + this.dateTime = aDateTime; + } + + /** + * get the name. + * + * @return name + */ + public String name() { + return name; + } + + + /** + * Get the date time. + * + * @return date time + */ + public LocalDateTime dateTime() { + return dateTime; + } + + +} diff --git a/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/Main.java b/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/Main.java new file mode 100644 index 00000000000..c1de8bcd9e0 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/Main.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.integrations.eclipsestore.greetings.se; + +import io.helidon.config.ClasspathConfigSource; +import io.helidon.config.Config; +import io.helidon.logging.common.LogConfig; +import io.helidon.webserver.WebServer; +import io.helidon.webserver.WebServerConfig; +import io.helidon.webserver.http.HttpRouting; + +/** + * Eclipsestore demo with a simple rest application. + */ +public final class Main { + + /** + * Cannot be instantiated. + */ + private Main() { + } + + /** + * Application main entry point. + * + * @param args command line arguments. + */ + public static void main(final String[] args) { + WebServerConfig.Builder builder = WebServer.builder(); + setup(builder); + WebServer server = builder.build().start(); + System.out.println( + "WEB server is up! http://localhost:" + server.port() + "/greet"); + } + + static void setup(final WebServerConfig.Builder server) { + LogConfig.configureRuntime(); + Config config = + Config.builder() + .addSource(ClasspathConfigSource.create("/application.yaml")) + .build(); + + // Build server with JSONP support + server.config(config.get("server")) + .routing(r -> routing(r, config)); + } + + /** + * Setup routing. + * + * @param routing routing builder + * @param config configuration of this server + */ + static void routing(final HttpRouting.Builder routing, + final Config config) { + GreetingService greetService = new GreetingService(config); + routing.register("/greet", greetService); + } +} diff --git a/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/package-info.java b/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/package-info.java new file mode 100644 index 00000000000..d26bf828763 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-se/src/main/java/io/helidon/examples/integrations/eclipsestore/greetings/se/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * An example that uses Eclipsestore to persist a log entry for every greeting. + */ +package io.helidon.examples.integrations.eclipsestore.greetings.se; diff --git a/examples/integrations/eclipsestore/greetings-se/src/main/resources/application.yaml b/examples/integrations/eclipsestore/greetings-se/src/main/resources/application.yaml new file mode 100644 index 00000000000..55813458514 --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-se/src/main/resources/application.yaml @@ -0,0 +1,26 @@ +# +# Copyright (c) 2021, 2024 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +app: + greeting: "Hello" + +server: + port: 8080 + host: 0.0.0.0 + +eclipsestore: + channel-count: 4 + housekeeping-interval: 2000ms diff --git a/examples/integrations/eclipsestore/greetings-se/src/test/java/io/helidon/examples/integrations/eclipsestore/greetings/se/EclipseStoreExampleGreetingsSeTest.java b/examples/integrations/eclipsestore/greetings-se/src/test/java/io/helidon/examples/integrations/eclipsestore/greetings/se/EclipseStoreExampleGreetingsSeTest.java new file mode 100644 index 00000000000..e2ee0899e3f --- /dev/null +++ b/examples/integrations/eclipsestore/greetings-se/src/test/java/io/helidon/examples/integrations/eclipsestore/greetings/se/EclipseStoreExampleGreetingsSeTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.integrations.eclipsestore.greetings.se; + +import java.nio.file.Path; + +import io.helidon.webserver.testing.junit5.ServerTest; +import io.helidon.webserver.testing.junit5.SetUpServer; +import io.helidon.webclient.http1.Http1Client; +import io.helidon.webclient.http1.Http1ClientResponse; +import io.helidon.webserver.WebServerConfig; + +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +@ServerTest +public class EclipseStoreExampleGreetingsSeTest { + + @TempDir + static Path tempDir; + + private final Http1Client client; + + public EclipseStoreExampleGreetingsSeTest(Http1Client client) { + this.client = client; + } + + @SetUpServer + static void setup(WebServerConfig.Builder server) { + System.setProperty("eclipsestore.storage-directory", tempDir.toString()); + Main.setup(server); + } + + @Test + void testExample() { + try (Http1ClientResponse response = client.get("/greet/Joe").request()) { + assertThat(response.as(JsonObject.class).getString("message"), is("Hello Joe!")); + } + + try (Http1ClientResponse response = client.get("/greet/logs").request()) { + JsonArray jsonArray = response.as(JsonArray.class); + assertThat(jsonArray.get(0).asJsonObject().getString("name"), is("Joe")); + assertThat(jsonArray.get(0).asJsonObject().getString("time"), notNullValue()); + } + } +} diff --git a/examples/integrations/eclipsestore/pom.xml b/examples/integrations/eclipsestore/pom.xml new file mode 100644 index 00000000000..198a220a816 --- /dev/null +++ b/examples/integrations/eclipsestore/pom.xml @@ -0,0 +1,38 @@ + + + + 4.0.0 + + io.helidon.examples.integrations + helidon-examples-integrations-project + 4.0.0-SNAPSHOT + + + io.helidon.examples.integrations.eclipsestore + helidon-examples-integrations-eclipsestore-project + Helidon Examples Integration EclipseStore + pom + + + greetings-se + greetings-mp + + diff --git a/examples/integrations/pom.xml b/examples/integrations/pom.xml index abead6217ee..9bbee7b7505 100644 --- a/examples/integrations/pom.xml +++ b/examples/integrations/pom.xml @@ -38,6 +38,7 @@ oci vault microstream + eclipsestore diff --git a/integrations/eclipsestore/README.md b/integrations/eclipsestore/README.md new file mode 100644 index 00000000000..4462f5c3bfa --- /dev/null +++ b/integrations/eclipsestore/README.md @@ -0,0 +1,156 @@ +# Eclipse Store integration with Helidon + +This projects adds [Eclipse Store](https://https://eclipsestore.io/) support to Helidon. + +[Eclipse Store](https://eclipsestore.io/) is the successor of [Microstream](https://microstream.one), both of which are integrated into Helidon as +extensions now, enabling smooth transition. + +The official [Eclipse Store documentation](https://docs.eclipsestore.io) can be found here. + +## helidon-integrations-eclipsestore + +Adds basic support for Eclipse Store + +### Prerequisites + +Use the following maven dependency + +``` + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore + +``` + +### API + +Use the EmbeddedStorageManagerBuilder to create a Eclipse Store instance: + +``` +EmbeddedStorageManager embeddedStorageManager = EmbeddedStorageManagerBuilder + .builder() + .build(); +``` + +Configuration can either be done by the builders methods or by supplying a helidon configuration node + +``` +Config config = Config.create(); + +EmbeddedStorageManager embeddedStorageManager = EmbeddedStorageManagerBuilder + .builder() + .config(config) + .build(); +``` + +for a list of all possible properties +see [Eclipse Store configuration properties](https://docs.eclipsestore.io/manual/storage/configuration/properties.html) + +### CDI extension for Eclipse Store + +the example below shows how to create a Eclipse Store instance using a provided configuration. + +``` +private EmbeddedStorageManager storage; + +@Inject +public YourConstructor(@EclipseStoreStorage(configNode = "org.eclipse.store.storage.greetings")EmbeddedStorageManager storage) { + super(); + this.storage = storage; +} +``` + +## helidon-integrations-eclipsestore-cache + +Adds basic support for the Eclipse Store JCache implementation + +### Prerequisites + +Use the following maven dependency + +``` + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-cache + +``` + +### API + +Use the CacheBuilder to create Eclipse Store JCache instance: + +Create a CacheConfiguration first + +``` +CacheConfiguration cacheConfig = EclipseStoreCacheConfigurationBuilder + .builder(config.get("cache"), Integer.class, String.class).build(); +``` + +Then build the cache + +``` +Cache cache = CacheBuilder.builder(cacheConfig, Integer.class, String.class).build("myCache"); +``` + +Configuration can either be done by the EclipseStoreCacheConfigurationBuilder or by supplying a helidon configuration node + +``` +Config config = Config.create(); + +Cache cache = CacheBuilder.create("myCache", config, Integer.class, String.class); + +``` + +for a list of all possible properties +see [Eclipse Store Cache configuration properties](https://docs.eclipsestore.io) + +### CDI extension for Eclipse Store + +the example below shows how to create a Eclipse Store Cache instance using a provided configuration. + +``` +private Cache cache; + +@Inject +public YourConstructor(@EclipseStoreCache(configNode = "org.eclipse.store.cache", name = "myCache") Cache cache) { + this.cache = cache; +} +``` + +## helidon-integrations-eclipsestore-health + +This module provides helpers to create basic health checks for Eclipse Store + +### Prerequisites + +Use the following maven dependency + +``` + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-health + +``` + +### Usage + +Register an instance of EclipseStoreHealthCheck to your server to provide a HealthCheck for a specific eclipse store instance. + +## helidon-integrations-eclipsestore-metrics + +This module provides helpers to create a set of default metrics for Eclipse Store + +### Prerequisites + +Use the following maven dependency + +``` + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-metrics + +``` + +### Usage + +The EclipseStoreMetricsSupport class provides a set of default metrics diff --git a/integrations/eclipsestore/cache/pom.xml b/integrations/eclipsestore/cache/pom.xml new file mode 100644 index 00000000000..d52fe257bec --- /dev/null +++ b/integrations/eclipsestore/cache/pom.xml @@ -0,0 +1,70 @@ + + + + 4.0.0 + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-project + 4.0.0-SNAPSHOT + + + helidon-integrations-eclipsestore-cache + Helidon Integrations Eclipse Store Cache + jar + + Eclipse Store JCache implementation + + + + org.eclipse.store + cache + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore + + + io.helidon.config + helidon-config-yaml + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + org.mockito + mockito-core + test + + + io.helidon.common.features + helidon-common-features-api + + + + diff --git a/integrations/eclipsestore/cache/src/main/java/io/helidon/integrations/eclipsestore/cache/CacheBuilder.java b/integrations/eclipsestore/cache/src/main/java/io/helidon/integrations/eclipsestore/cache/CacheBuilder.java new file mode 100644 index 00000000000..a66eb1c88cb --- /dev/null +++ b/integrations/eclipsestore/cache/src/main/java/io/helidon/integrations/eclipsestore/cache/CacheBuilder.java @@ -0,0 +1,96 @@ +/* + * 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.integrations.eclipsestore.cache; + +import javax.cache.Caching; + +import io.helidon.config.Config; + +import org.eclipse.store.cache.types.Cache; +import org.eclipse.store.cache.types.CacheConfiguration; +import org.eclipse.store.cache.types.CacheManager; +import org.eclipse.store.cache.types.CachingProvider; + +/** + * Builder for a Eclipse Store JCache instance. + * + * @param type of the cache key + * @param type of the cache value + */ +public class CacheBuilder { + private static final String ECLIPSESTORE_CACHING_PROVIDER = "org.eclipse.store.cache.types.CachingProvider"; + + private final CachingProvider provider; + private final CacheManager cacheManager; + private final CacheConfiguration configuration; + + protected CacheBuilder(CacheConfiguration configuration) { + super(); + + this.configuration = configuration; + this.provider = (CachingProvider) Caching.getCachingProvider(ECLIPSESTORE_CACHING_PROVIDER); + this.cacheManager = provider.getCacheManager(); + } + + protected CacheBuilder(Class keyType, Class valueType) { + this.provider = (CachingProvider) Caching.getCachingProvider(ECLIPSESTORE_CACHING_PROVIDER); + this.cacheManager = provider.getCacheManager(); + this.configuration = CacheConfiguration.Builder(keyType, valueType).build(); + } + + /** + * Create a new cache builder using the provided eclipstore cache configuration. + * + * @param type of the cache key + * @param type of the cache value + * @param configuration CacheConfiguration + * @param keyType class of the cache key + * @param valueType class of the cache key + * @return cache builder + */ + public static CacheBuilder builder(CacheConfiguration configuration, + Class keyType, + Class valueType) { + return new CacheBuilder<>(configuration); + } + + /** + * Create a named cache using the provided helidon configuration. + * + * @param name the cache name + * @param config the helidon configuration + * @param keyType class of the cache key + * @param valueType class of the cache key + * @return the new cache instance + */ + public static Cache create(String name, Config config, Class keyType, Class valueType) { + + CacheConfiguration cacheConfig = EclipseStoreCacheConfigurationBuilder.builder(config, keyType, valueType) + .build(); + return new CacheBuilder<>(cacheConfig).build(name); + } + + /** + * Set the name of the cache. + * + * @param name the cache name + * @return the new cache instance + */ + public Cache build(String name) { + return cacheManager.createCache(name, configuration); + } +} diff --git a/integrations/eclipsestore/cache/src/main/java/io/helidon/integrations/eclipsestore/cache/ConfigException.java b/integrations/eclipsestore/cache/src/main/java/io/helidon/integrations/eclipsestore/cache/ConfigException.java new file mode 100644 index 00000000000..3167c9e8253 --- /dev/null +++ b/integrations/eclipsestore/cache/src/main/java/io/helidon/integrations/eclipsestore/cache/ConfigException.java @@ -0,0 +1,47 @@ +/* + * 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.integrations.eclipsestore.cache; + +import java.io.Serial; + +/** + * RuntimeException thrown in case of Eclipse Store Cache configuration problems. + */ +public class ConfigException extends RuntimeException { + @Serial + private static final long serialVersionUID = 1L; + + /** + * creates a new ConfigException. + * + * @param message exception message + * @param cause the cause + */ + public ConfigException(String message, Throwable cause) { + super(message, cause); + } + + /** + * creates a new ConfigException. + * + * @param message exception message + */ + public ConfigException(String message) { + super(message); + } + +} diff --git a/integrations/eclipsestore/cache/src/main/java/io/helidon/integrations/eclipsestore/cache/EclipseStoreCacheConfigurationBuilder.java b/integrations/eclipsestore/cache/src/main/java/io/helidon/integrations/eclipsestore/cache/EclipseStoreCacheConfigurationBuilder.java new file mode 100644 index 00000000000..de509279988 --- /dev/null +++ b/integrations/eclipsestore/cache/src/main/java/io/helidon/integrations/eclipsestore/cache/EclipseStoreCacheConfigurationBuilder.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.eclipsestore.cache; + +import javax.cache.configuration.CacheEntryListenerConfiguration; +import javax.cache.configuration.Factory; +import javax.cache.expiry.ExpiryPolicy; +import javax.cache.integration.CacheLoader; +import javax.cache.integration.CacheWriter; + +import io.helidon.config.Config; + +import org.eclipse.serializer.SerializerFoundation; +import org.eclipse.serializer.configuration.types.Configuration; +import org.eclipse.store.cache.types.CacheConfiguration; +import org.eclipse.store.cache.types.CacheConfiguration.Builder; +import org.eclipse.store.cache.types.CacheConfigurationBuilderConfigurationBased; +import org.eclipse.store.cache.types.CacheConfigurationPropertyNames; +import org.eclipse.store.cache.types.EvictionManager; + + + +/** + * Builder for Eclipse Store CacheConfigurations. + * + * @param type of the cache key + * @param type of the cache value + */ +public class EclipseStoreCacheConfigurationBuilder + implements CacheConfigurationPropertyNames, + CacheConfiguration.Builder, + io.helidon.common.Builder, CacheConfiguration> { + + private final Builder cacheConfigBuilder; + + protected EclipseStoreCacheConfigurationBuilder(Class keyType, Class valueType) { + super(); + cacheConfigBuilder = CacheConfiguration.Builder(keyType, valueType); + } + + protected EclipseStoreCacheConfigurationBuilder(Configuration configuration, Class keyType, Class valueType) { + super(); + cacheConfigBuilder = CacheConfigurationBuilderConfigurationBased.New().buildCacheConfiguration(configuration, + CacheConfiguration.Builder( + keyType, + valueType)); + } + + /** + * creates a new EclipseStoreCacheConfigurationBuilder using the supplied helidon configuration. + * + * @param config helidon configuration + * @return a new EclipseStoreCacheConfigurationBuilder + */ + public static EclipseStoreCacheConfigurationBuilder builder(Config config) { + return builder(config, null, null); + } + + /** + * Create a CacheConfiguration builder with default values. + * + * @param type of the cache key + * @param type of the cache value + * @param keyType type of the cache key + * @param valueType type of the cache value + * @return a new CacheConfiguration builder + */ + public static EclipseStoreCacheConfigurationBuilder builder(Class keyType, Class valueType) { + return new EclipseStoreCacheConfigurationBuilder<>(keyType, valueType); + } + + /** + * Create a CacheConfiguration builder initialized from the supplied helidon + * configuration node. + * + * @param type of the cache key + * @param type of the cache value + * @param config helidon configuration + * @param keyType type of the cache key + * @param valueType type of the cache value + * @return a new CacheConfiguration builder + */ + public static EclipseStoreCacheConfigurationBuilder builder(Config config, Class keyType, + Class valueType) { + org.eclipse.serializer.configuration.types.Configuration.Builder configurationBuilder = Configuration.Builder(); + if (config.exists()) { + config.detach().asMap().get().forEach(configurationBuilder::set); + } + + Configuration configuration = configurationBuilder.buildConfiguration(); + configuration.opt(KEY_TYPE).ifPresent((s) -> verifyType(s, keyType)); + configuration.opt(VALUE_TYPE).ifPresent((s) -> verifyType(s, valueType)); + + return new EclipseStoreCacheConfigurationBuilder<>(configuration, keyType, valueType); + } + + private static void verifyType(String typeName, Class actualType) { + if (!typeName.equals(actualType.getTypeName())) { + throw new ConfigException("Eclipse Store cache-config type mismatch, expected value from configuration: " + typeName + + " but got: " + actualType.getTypeName()); + } + } + + @Override + public CacheConfiguration build() { + return cacheConfigBuilder.build(); + } + + @Override + public EclipseStoreCacheConfigurationBuilder readThrough(boolean readTrough) { + cacheConfigBuilder.readThrough(readTrough); + return this; + } + + @Override + public EclipseStoreCacheConfigurationBuilder writeThrough(boolean writeThrough) { + cacheConfigBuilder.writeThrough(writeThrough); + return this; + } + + @Override + public EclipseStoreCacheConfigurationBuilder storeByValue(boolean storeByValue) { + cacheConfigBuilder.storeByValue(storeByValue); + return this; + } + + @Override + public EclipseStoreCacheConfigurationBuilder enableStatistics(boolean enableStatistics) { + cacheConfigBuilder.enableStatistics(enableStatistics); + return this; + } + + @Override + public EclipseStoreCacheConfigurationBuilder enableManagement(boolean enableManagement) { + cacheConfigBuilder.enableManagement(enableManagement); + return this; + } + + @Override + public EclipseStoreCacheConfigurationBuilder expiryPolicyFactory(Factory expiryPolicyFactory) { + cacheConfigBuilder.expiryPolicyFactory(expiryPolicyFactory); + return this; + } + + @Override + public EclipseStoreCacheConfigurationBuilder evictionManagerFactory( + Factory> evictionManagerFactory) { + cacheConfigBuilder.evictionManagerFactory(evictionManagerFactory); + return this; + } + + @Override + public EclipseStoreCacheConfigurationBuilder cacheLoaderFactory( + Factory> cacheLoaderFactory) { + cacheConfigBuilder.cacheLoaderFactory(cacheLoaderFactory); + return this; + } + + @Override + public EclipseStoreCacheConfigurationBuilder cacheWriterFactory( + Factory> cacheWriterFactory) { + cacheConfigBuilder.cacheWriterFactory(cacheWriterFactory); + return this; + } + + @Override + public Builder serializerFoundation(SerializerFoundation serializerFoundation) { + return cacheConfigBuilder.serializerFoundation(serializerFoundation); + } + + @Override + public Builder addListenerConfiguration(CacheEntryListenerConfiguration listenerConfiguration) { + cacheConfigBuilder.addListenerConfiguration(listenerConfiguration); + return this; + } +} diff --git a/integrations/eclipsestore/cache/src/main/java/io/helidon/integrations/eclipsestore/cache/package-info.java b/integrations/eclipsestore/cache/src/main/java/io/helidon/integrations/eclipsestore/cache/package-info.java new file mode 100644 index 00000000000..d2caed57795 --- /dev/null +++ b/integrations/eclipsestore/cache/src/main/java/io/helidon/integrations/eclipsestore/cache/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides support for Eclipse Store Cache features integration. + */ +package io.helidon.integrations.eclipsestore.cache; diff --git a/integrations/eclipsestore/cache/src/main/java/module-info.java b/integrations/eclipsestore/cache/src/main/java/module-info.java new file mode 100644 index 00000000000..300f1817a91 --- /dev/null +++ b/integrations/eclipsestore/cache/src/main/java/module-info.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import io.helidon.common.features.api.Aot; +import io.helidon.common.features.api.Feature; +import io.helidon.common.features.api.HelidonFlavor; + +/** + * Provides support for EclipseStore Cache features integration. + */ +@SuppressWarnings({"requires-automatic", "requires-transitive-automatic"}) +@Feature(value = "Eclipse Store Cache", + description = "Eclipse Store Cache Integration", + in = HelidonFlavor.SE, + path = {"EclipseStore", "Cache"} +) +@Aot(false) +module io.helidon.integrations.eclipsestore.cache { + requires transitive cache.api; + requires transitive io.helidon.integrations.eclipsestore; + requires transitive org.eclipse.store.cache; + requires transitive org.eclipse.serializer; + + exports io.helidon.integrations.eclipsestore.cache; +} diff --git a/integrations/eclipsestore/cache/src/main/resources/META-INF/native-image/native-image.properties b/integrations/eclipsestore/cache/src/main/resources/META-INF/native-image/native-image.properties new file mode 100644 index 00000000000..dcec89f6d9e --- /dev/null +++ b/integrations/eclipsestore/cache/src/main/resources/META-INF/native-image/native-image.properties @@ -0,0 +1,16 @@ +# +# Copyright (c) 2023, 2024 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +Args=--initialize-at-run-time=org.eclipse.store.cache.types.MBeanServerUtils diff --git a/integrations/eclipsestore/cache/src/test/java/io/helidon/integrations/eclipsestore/cache/CacheTest.java b/integrations/eclipsestore/cache/src/test/java/io/helidon/integrations/eclipsestore/cache/CacheTest.java new file mode 100644 index 00000000000..5e967ef76ef --- /dev/null +++ b/integrations/eclipsestore/cache/src/test/java/io/helidon/integrations/eclipsestore/cache/CacheTest.java @@ -0,0 +1,63 @@ +/* + * 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.integrations.eclipsestore.cache; + +import io.helidon.config.ClasspathConfigSource; +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import org.eclipse.store.cache.types.Cache; +import org.eclipse.store.cache.types.CacheConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; +import java.util.Map; + +class CacheTest { + + @TempDir + Path tempDir; + + @Test + void createCacheTest() { + CacheConfiguration config = EclipseStoreCacheConfigurationBuilder.builder(Integer.class, String.class) + .build(); + CacheBuilder builder = CacheBuilder.builder(config, Integer.class, String.class); + Cache cache = builder.build("testCache"); + + cache.put(1, "Hello"); + + cache.close(); + } + + @Test + void createCacheFromConfigTest() { + Config helidonConfig = Config.builder().addSource(ClasspathConfigSource.create("/eclipsestoreCacheConfig.yml")) + .addSource(ConfigSources.create(Map.of("cache.eclipsestore.storage.storage-directory", tempDir.toString()))) + .build(); + + CacheConfiguration config = EclipseStoreCacheConfigurationBuilder + .builder(helidonConfig.get("cache.eclipsestore"), Integer.class, String.class) + .build(); + + Cache cache = CacheBuilder.builder(config, Integer.class, String.class).build("Cache_IntStr"); + + cache.put(1, "Hello"); + + cache.close(); + } +} diff --git a/integrations/eclipsestore/cache/src/test/java/io/helidon/integrations/eclipsestore/cache/ConfigurationTest.java b/integrations/eclipsestore/cache/src/test/java/io/helidon/integrations/eclipsestore/cache/ConfigurationTest.java new file mode 100644 index 00000000000..6180d0d5ca6 --- /dev/null +++ b/integrations/eclipsestore/cache/src/test/java/io/helidon/integrations/eclipsestore/cache/ConfigurationTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.eclipsestore.cache; + +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import org.eclipse.store.cache.types.CacheConfiguration; +import org.eclipse.store.cache.types.EvictionManager; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.cache.configuration.Factory; +import javax.cache.expiry.ExpiryPolicy; +import javax.cache.integration.CacheLoader; +import javax.cache.integration.CacheWriter; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertAll; + +class ConfigurationTest { + + /** + * Test if all default properties are set. + */ + @Test + void defaultValuesTest() { + CacheConfiguration cacheConfig = EclipseStoreCacheConfigurationBuilder + .builder(Integer.class, String.class).build(); + + assertAll(() -> assertThat("getKeyType", cacheConfig.getKeyType(), typeCompatibleWith(Integer.class)), + () -> assertThat("getValueType", cacheConfig.getValueType(), typeCompatibleWith(String.class)), + () -> assertThat("isManagementEnabled", cacheConfig.isManagementEnabled(), is(false)), + () -> assertThat("isStatisticsEnabled", cacheConfig.isStatisticsEnabled(), is(false)), + () -> assertThat("isReadThrough", cacheConfig.isReadThrough(), is(false)), + () -> assertThat("isWriteThrough", cacheConfig.isWriteThrough(), is(false)), + () -> assertThat("isStoreByValue", cacheConfig.isStoreByValue(), is(false)), + () -> assertThat("getExpiryPolicyFactory", cacheConfig.getExpiryPolicyFactory(), + is(CacheConfiguration.DefaultExpiryPolicyFactory())), + () -> assertThat("getEvictionManagerFactory", cacheConfig.getEvictionManagerFactory(), + is(CacheConfiguration.DefaultEvictionManagerFactory())), + () -> assertThat("getCacheLoaderFactory", cacheConfig.getCacheLoaderFactory(), nullValue()), + () -> assertThat("getCacheWriterFactory", cacheConfig.getCacheWriterFactory(), nullValue()), + () -> assertThat("getCacheEntryListenerConfigurations", + cacheConfig.getCacheEntryListenerConfigurations(), emptyIterable())); + } + + /** + * Test if simple configuration values are applied. This test does not check all + * values + */ + @Test + void configValuesTest() { + Map source = Map.of("cache.management-enabled", "true", "cache.statistics-enabled", "true", + "cache.store-by-value", "true"); + + Config config = Config.builder().addSource(ConfigSources.create(source).build()).build(); + + CacheConfiguration cacheConfig = EclipseStoreCacheConfigurationBuilder + .builder(config.get("cache"), Long.class, String.class).build(); + + assertAll(() -> assertThat("getKeyType", cacheConfig.getKeyType(), typeCompatibleWith(Long.class)), + () -> assertThat("getValueType", cacheConfig.getValueType(), typeCompatibleWith(String.class)), + () -> assertThat("isManagementEnabled", cacheConfig.isManagementEnabled(), is(true)), + () -> assertThat("isStatisticsEnabled", cacheConfig.isStatisticsEnabled(), is(true)), + () -> assertThat("isStoreByValue", cacheConfig.isStoreByValue(), is(true))); + } + + /** + * Test if settings from config can be altered by code + */ + @Test + void applyChangeTest() { + + Map source = Map.of("cache.management-enabled", "true", "cache.statistics-enabled", "true", + "cache.store-by-value", "true"); + + Config config = Config.builder().addSource(ConfigSources.create(source).build()).build(); + + CacheConfiguration cacheConfig = EclipseStoreCacheConfigurationBuilder + .builder(config.get("cache"), Long.class, String.class).disableManagement().storeByValue(false).build(); + + assertAll(() -> assertThat("isManagementEnabled", cacheConfig.isManagementEnabled(), is(false)), + () -> assertThat("isStatisticsEnabled", cacheConfig.isStatisticsEnabled(), is(true)), + () -> assertThat("isStoreByValue", cacheConfig.isStoreByValue(), is(false))); + } + + @Test + void cacheLoaderFactoryTest() { + @SuppressWarnings("unchecked") + Factory> cacheLoaderFactory = Mockito.mock(Factory.class); + + CacheConfiguration cacheConfig = EclipseStoreCacheConfigurationBuilder + .builder(Integer.class, String.class).cacheLoaderFactory(cacheLoaderFactory).build(); + + assertThat(cacheConfig.getCacheLoaderFactory(), sameInstance(cacheLoaderFactory)); + } + + @Test + void cacheWriterFactoryTest() { + @SuppressWarnings("unchecked") + Factory> cacheWriterFactory = Mockito.mock(Factory.class); + + CacheConfiguration cacheConfig = EclipseStoreCacheConfigurationBuilder + .builder(Integer.class, String.class).cacheWriterFactory(cacheWriterFactory).build(); + + assertThat(cacheConfig.getCacheWriterFactory(), sameInstance(cacheWriterFactory)); + } + + @Test + void expiryPolicyFactoryTest() { + @SuppressWarnings("unchecked") + Factory expiryPolicyFactory = Mockito.mock(Factory.class); + + CacheConfiguration cacheConfig = EclipseStoreCacheConfigurationBuilder + .builder(Integer.class, String.class).expiryPolicyFactory(expiryPolicyFactory).build(); + + assertThat(cacheConfig.getExpiryPolicyFactory(), sameInstance(expiryPolicyFactory)); + } + + @Test + void evictionManagerFactoryTest() { + @SuppressWarnings("unchecked") + Factory> evictionManagerFactory = Mockito.mock(Factory.class); + + CacheConfiguration cacheConfig = EclipseStoreCacheConfigurationBuilder + .builder(Integer.class, String.class).evictionManagerFactory(evictionManagerFactory).build(); + + assertThat(cacheConfig.getEvictionManagerFactory(), sameInstance(evictionManagerFactory)); + } + +} diff --git a/integrations/eclipsestore/cache/src/test/resources/eclipsestoreCacheConfig.yml b/integrations/eclipsestore/cache/src/test/resources/eclipsestoreCacheConfig.yml new file mode 100644 index 00000000000..abe7683c123 --- /dev/null +++ b/integrations/eclipsestore/cache/src/test/resources/eclipsestoreCacheConfig.yml @@ -0,0 +1,24 @@ +# +# Copyright (c) 2021, 2024 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +cache: + eclipsestore: + missing_cache_strategy: create + readThrough: true + writeThrough: true + storage: + baseDirectory: ~/cache-data + channelCount: 4 diff --git a/integrations/eclipsestore/cdi/pom.xml b/integrations/eclipsestore/cdi/pom.xml new file mode 100644 index 00000000000..035012d7484 --- /dev/null +++ b/integrations/eclipsestore/cdi/pom.xml @@ -0,0 +1,96 @@ + + + + 4.0.0 + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-project + 4.0.0-SNAPSHOT + + + helidon-integrations-eclipsestore-cdi + Helidon Integrations Eclipse Store CDI + jar + + Eclipse Store JCache implementation + + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-cache + + + io.helidon.common.features + helidon-common-features-api + true + + + jakarta.annotation + jakarta.annotation-api + provided + + + jakarta.enterprise + jakarta.enterprise.cdi-api + provided + + + + io.helidon.microprofile.cdi + helidon-microprofile-cdi + test + + + io.helidon.microprofile.testing + helidon-microprofile-testing-junit5 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.helidon.common.features + helidon-common-features-processor + ${helidon.version} + + + + + + + diff --git a/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/CacheExtension.java b/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/CacheExtension.java new file mode 100644 index 00000000000..46b185bef0a --- /dev/null +++ b/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/CacheExtension.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.eclipsestore.cdi; + + + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import io.helidon.common.GenericType; +import io.helidon.config.Config; +import io.helidon.integrations.eclipsestore.cache.CacheBuilder; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.AfterBeanDiscovery; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.enterprise.inject.spi.ProcessInjectionPoint; +import org.eclipse.store.cache.types.Cache; + +import static jakarta.interceptor.Interceptor.Priority.PLATFORM_BEFORE; + +/** + * An {@link Extension} that arranges for named {@link EclipseStoreCache} + * injection points to be satisfied. + */ +public class CacheExtension implements Extension { + + private final Set cacheBeans; + + private Config config; + + /** + * Creates a new {@link CacheExtension}. + */ + public CacheExtension() { + super(); + cacheBeans = new HashSet<>(); + } + + private void configure(@Observes @Priority(PLATFORM_BEFORE) Config config) { + this.config = config; + } + + /* + * Collect all injection points qualifiers for EclipseStore Cache + */ + private > void processInjectionPoint( + @Observes final ProcessInjectionPoint event) { + if (event != null) { + final InjectionPoint injectionPoint = event.getInjectionPoint(); + if (injectionPoint != null) { + if (injectionPoint.getAnnotated().isAnnotationPresent(EclipseStoreCache.class)) { + this.cacheBeans.add( + new Descriptor(injectionPoint.getQualifiers(), (ParameterizedType) injectionPoint.getType())); + } + } + } + } + + /* + * create EmbeddedStorageManager beans + */ + private void addBeans(@Observes final AfterBeanDiscovery event, final BeanManager beanManager) { + if (event != null && beanManager != null) { + if (!this.cacheBeans.isEmpty()) { + for (final Descriptor entry : this.cacheBeans) { + assert entry != null; + // create Eclipse Store Cache bean + final Set qualifiers = entry.getAnnotations(); + assert qualifiers != null; + assert !qualifiers.isEmpty(); + + ParameterizedType types = entry.getTypes(); + GenericType keyType = GenericType.create(types.getActualTypeArguments()[0]); + GenericType valueType = GenericType.create(types.getActualTypeArguments()[1]); + String name = getName(qualifiers); + + event.>addBean() + .qualifiers(qualifiers) + .scope(ApplicationScoped.class) + .addTransitiveTypeClosure(Cache.class) + .addTypes(types) + .createWith(cc -> CacheBuilder.create(name, getConfigNode(qualifiers), keyType.rawType(), + valueType.rawType())) + .destroyWith((cache, context) -> cache.close()); + } + } + } + } + + /* + * Get the config node that matches the name supplied by @EclipseStoreStorage + * annotation if no name is available the full helidon config is returned + */ + private Config getConfigNode(Set qualifiers) { + Optional optAnnotation = qualifiers.stream().filter(e -> e instanceof EclipseStoreCache).findFirst(); + if (optAnnotation.isPresent()) { + EclipseStoreCache annotation = (EclipseStoreCache) optAnnotation.get(); + String name = annotation.configNode(); + return config.get(name); + } + return null; + } + + /* + * Get the name supplied by @EclipseStoreStorage + * annotation if no name is available null is returned + */ + private String getName(Set qualifiers) { + Optional optAnnotation = qualifiers.stream().filter(e -> e instanceof EclipseStoreCache).findFirst(); + if (optAnnotation.isPresent()) { + EclipseStoreCache annotation = (EclipseStoreCache) optAnnotation.get(); + return annotation.name(); + } + return null; + } + + private static class Descriptor { + + private final Set annotations; + private final ParameterizedType types; + + private Descriptor(Set cacheBeans, ParameterizedType types) { + super(); + this.annotations = cacheBeans; + this.types = types; + } + + public Set getAnnotations() { + return annotations; + } + + public ParameterizedType getTypes() { + return types; + } + + @Override + public int hashCode() { + return Objects.hash(annotations, types); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Descriptor other = (Descriptor) obj; + return Objects.equals(annotations, other.annotations) && Objects.equals(types, other.types); + } + } +} diff --git a/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/EclipseStoreCache.java b/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/EclipseStoreCache.java new file mode 100644 index 00000000000..b4653f66caa --- /dev/null +++ b/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/EclipseStoreCache.java @@ -0,0 +1,58 @@ +/* + * 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.integrations.eclipsestore.cdi; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.inject.Qualifier; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + + + +/** + * Creates a cache based upon the Eclipse Store JCache implementation. + *
See Eclipse Store JCache + *

+ * Specify the cache name by the name property. + *

+ * The configNode property expects an existing Helidon config node providing configuration properties for the cache. + *
See Eclipse Store JCache configuration + *
If not provided the properties below "org.eclipse.store.cache.default" will be used if existing. + * Otherwise the build in defaults are applied. + */ +@Qualifier +@Retention(RUNTIME) +@Target({PARAMETER, FIELD}) +public @interface EclipseStoreCache { + /** + * Specifies the configuration node used to configure the EmbeddedStorageManager instance to be created. + * + * @return the configuration node + */ + String configNode() default "org.eclipse.store.cache.default"; + + /** + * Specifies the cache name. + * + * @return the cache name + */ + String name(); +} diff --git a/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/EclipseStoreStorage.java b/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/EclipseStoreStorage.java new file mode 100644 index 00000000000..bcd356a42d9 --- /dev/null +++ b/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/EclipseStoreStorage.java @@ -0,0 +1,43 @@ +/* + * 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.integrations.eclipsestore.cdi; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.inject.Qualifier; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + + + +/** + * Qualifier annotation for Eclise Store EmbeddedStorageManager. + */ +@Qualifier +@Retention(RUNTIME) +@Target({FIELD, PARAMETER}) +public @interface EclipseStoreStorage { + /** + * Specifies the configuration node used to configure the EmbeddedStorageManager instance to be created. + * + * @return the config node + */ + String configNode(); +} diff --git a/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/EmbeddedStorageManagerExtension.java b/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/EmbeddedStorageManagerExtension.java new file mode 100644 index 00000000000..97f1a76038d --- /dev/null +++ b/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/EmbeddedStorageManagerExtension.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.eclipsestore.cdi; + +import java.lang.annotation.Annotation; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; + +import io.helidon.config.Config; +import io.helidon.integrations.eclipsestore.core.EmbeddedStorageManagerBuilder; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.AfterBeanDiscovery; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.enterprise.inject.spi.ProcessInjectionPoint; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; + + +import static jakarta.interceptor.Interceptor.Priority.PLATFORM_BEFORE; + +/** + * An {@link Extension} that arranges for named {@link EclipseStoreStorage} + * injection points to be satisfied. + */ +public class EmbeddedStorageManagerExtension implements Extension { + + private final Map, Object> embeddedStorageBeans; + + private Config config; + + /** + * Creates a new {@link EmbeddedStorageManagerExtension}. + */ + public EmbeddedStorageManagerExtension() { + super(); + embeddedStorageBeans = new HashMap<>(); + } + + private void configure(@Observes @Priority(PLATFORM_BEFORE) Config config) { + this.config = config; + } + + /* + * Collect all injection points qualifiers for EmbeddedStorageManagers + */ + private void processInjectionPoint(@Observes final ProcessInjectionPoint event) { + if (event != null) { + final InjectionPoint injectionPoint = event.getInjectionPoint(); + if (injectionPoint != null) { + this.embeddedStorageBeans.put(injectionPoint.getQualifiers(), null); + } + } + } + + /* + * create EmbeddedStorageManager beans + */ + private void addBeans(@Observes final AfterBeanDiscovery event, final BeanManager beanManager) { + if (event != null && beanManager != null) { + if (!this.embeddedStorageBeans.isEmpty()) { + for (final Entry, ?> entry : this.embeddedStorageBeans.entrySet()) { + assert entry != null; + if (entry.getValue() == null) { + // create EmbeddedStorageManager bean + final Set qualifiers = entry.getKey(); + assert qualifiers != null; + assert !qualifiers.isEmpty(); + event.addBean().scope(ApplicationScoped.class) + .addTransitiveTypeClosure(EmbeddedStorageManager.class) + .beanClass(EmbeddedStorageManager.class) + .qualifiers(qualifiers) + .createWith(cc -> EmbeddedStorageManagerBuilder.create(getConfigNode(qualifiers)).start()) + .destroyWith((storageManager, context) -> storageManager.shutdown()); + } + } + } + } + } + + /* + * Get the config node that matches the name supplied by @EclipseStoreStorage annotation + * if no name is available the full helidon config is returned + */ + private Config getConfigNode(Set qualifiers) { + Optional optAnnotation = qualifiers + .stream() + .filter(e -> e instanceof EclipseStoreStorage) + .findFirst(); + if (optAnnotation.isPresent()) { + EclipseStoreStorage value = (EclipseStoreStorage) optAnnotation.get(); + String name = value.configNode(); + return config.get(name); + } + return config; + } +} diff --git a/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/package-info.java b/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/package-info.java new file mode 100644 index 00000000000..5fabc6cd6c4 --- /dev/null +++ b/integrations/eclipsestore/cdi/src/main/java/io/helidon/integrations/eclipsestore/cdi/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides CDI support for Eclipse Store integration. + */ +package io.helidon.integrations.eclipsestore.cdi; diff --git a/integrations/eclipsestore/cdi/src/main/java/module-info.java b/integrations/eclipsestore/cdi/src/main/java/module-info.java new file mode 100644 index 00000000000..47c5dfc7057 --- /dev/null +++ b/integrations/eclipsestore/cdi/src/main/java/module-info.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import io.helidon.common.features.api.Aot; +import io.helidon.common.features.api.Feature; +import io.helidon.common.features.api.HelidonFlavor; + + +@Feature(value = "Eclipse Store", + description = "Eclipse Store Integration", + in = HelidonFlavor.MP, + path = {"EclipseStore", "CDI"} +) +@Aot(false) +@SuppressWarnings({"requires-automatic", "requires-transitive-automatic"}) +module io.helidon.integrations.eclipsestore.cdi { + requires jakarta.annotation; + requires transitive cache.api; + requires transitive jakarta.cdi; + requires transitive jakarta.inject; + + requires io.helidon.integrations.eclipsestore.cache; + exports io.helidon.integrations.eclipsestore.cdi; + + provides jakarta.enterprise.inject.spi.Extension + with io.helidon.integrations.eclipsestore.cdi.EmbeddedStorageManagerExtension, + io.helidon.integrations.eclipsestore.cdi.CacheExtension; + +} diff --git a/integrations/eclipsestore/cdi/src/test/java/io/helidon/integrations/eclipsestore/cdi/CacheManagerExtensionTest.java b/integrations/eclipsestore/cdi/src/test/java/io/helidon/integrations/eclipsestore/cdi/CacheManagerExtensionTest.java new file mode 100644 index 00000000000..29de8bf21fc --- /dev/null +++ b/integrations/eclipsestore/cdi/src/test/java/io/helidon/integrations/eclipsestore/cdi/CacheManagerExtensionTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.eclipsestore.cdi; + +import io.helidon.integrations.eclipsestore.cache.ConfigException; +import io.helidon.microprofile.testing.junit5.AddConfig; +import io.helidon.microprofile.testing.junit5.HelidonTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +import javax.cache.Cache; +import java.time.Duration; +import java.util.HashMap; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@HelidonTest +@AddConfig(key = "org.eclipse.store.cache-HashMap.key-type", value = "java.lang.Integer") +@AddConfig(key = "org.eclipse.store.cache-HashMap.value-type", value = "java.util.HashMap") +@AddConfig(key = "org.eclipse.store.cache-wrongTypes.key-type", value = "java.lang.Integer") +@AddConfig(key = "org.eclipse.store.cache-wrongTypes.value-type", value = "java.lang.String") +class CacheManagerExtensionTest { + + @Inject + @EclipseStoreCache(name = "intStrCache") + Cache cacheIntStr; + + @Inject + @EclipseStoreCache(name = "intStrCache_2") + Cache cacheIntStr_2; + + @Inject + @EclipseStoreCache(name = "intStrCache") + Cache cacheIntStr_3; + + @Inject + @EclipseStoreCache(configNode = "org.eclipse.store.cache-wrongTypes", name = "wrongKeyType") + Cache cacheWrongKeyType; + + @Inject + @EclipseStoreCache(configNode = "org.eclipse.store.cache-wrongTypes", name = "wrongValueType") + Cache cacheWrongValueType; + + @Inject + @EclipseStoreCache(configNode = "org.eclipse.store.cache-HashMap", name = "cacheHashMap") + Cache> cacheHashMap; + + @Test + void sameInstanceTest() { + assertThat(cacheIntStr, is(sameInstance(cacheIntStr_3))); + } + + @Test + void differentInstancesTest() { + assertThat(cacheIntStr, is(not(sameInstance(cacheIntStr_2)))); + } + + @Test + void createTestWithoutConfigTest() { + cacheIntStr.put(1, "Hello"); + assertThat(cacheIntStr.get(1), is("Hello")); + } + + @Test + void wrongValueTypeTest() { + assertThrows(ConfigException.class, () -> cacheWrongValueType.put(1, 42)); + } + + @Test + void wrongKeyTypeTest() { + assertThrows(ConfigException.class, () -> cacheWrongKeyType.put("1", "Hello")); + } + + @Test + void hashmapCacheTest() { + HashMap e1 = new HashMap<>(); + e1.put("Duration_1", Duration.ofDays(2)); + HashMap e2 = new HashMap<>(); + e1.put("Duration_1", Duration.ofSeconds(2)); + cacheHashMap.put(10, e1); + cacheHashMap.put(11, e2); + assertThat(cacheHashMap.get(10), sameInstance(e1)); + assertThat(cacheHashMap.get(11), sameInstance(e2)); + } + +} diff --git a/integrations/eclipsestore/cdi/src/test/java/io/helidon/integrations/eclipsestore/cdi/EclipseStoreExtensionTest.java b/integrations/eclipsestore/cdi/src/test/java/io/helidon/integrations/eclipsestore/cdi/EclipseStoreExtensionTest.java new file mode 100644 index 00000000000..acb47d19f9d --- /dev/null +++ b/integrations/eclipsestore/cdi/src/test/java/io/helidon/integrations/eclipsestore/cdi/EclipseStoreExtensionTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.eclipsestore.cdi; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import jakarta.inject.Inject; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; + +@HelidonTest +class EclipseStoreExtensionTest { + + @TempDir + static Path tempDir; + + @Inject + @EclipseStoreStorage(configNode = "org.eclipse.store.storage.my_storage") + EmbeddedStorageManager storage; + + @BeforeAll + static void beforeAll() { + System.setProperty("org.eclipse.store.storage.my_storage.storage-directory", tempDir.toString()); + } + + @AfterAll + static void afterAll() { + System.clearProperty("org.eclipse.store.storage.my_storage.storage-directory"); + } + + @Test + void testInjectedInstances() { + assertThat(storage, notNullValue()); + assertThat(storage.isRunning(), equalTo(true)); + } + +} diff --git a/integrations/eclipsestore/cdi/src/test/resources/META-INF/microprofile-config.properties b/integrations/eclipsestore/cdi/src/test/resources/META-INF/microprofile-config.properties new file mode 100644 index 00000000000..a6fc62ff468 --- /dev/null +++ b/integrations/eclipsestore/cdi/src/test/resources/META-INF/microprofile-config.properties @@ -0,0 +1,17 @@ +# +# Copyright (c) 2019, 2024 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +org.eclipse.store.storage.my_storage.channel-count=4 +org.eclipse.store.storage.my_storage.housekeeping-interval=2000ms diff --git a/integrations/eclipsestore/core/pom.xml b/integrations/eclipsestore/core/pom.xml new file mode 100644 index 00000000000..3589d5f1fa8 --- /dev/null +++ b/integrations/eclipsestore/core/pom.xml @@ -0,0 +1,90 @@ + + + + 4.0.0 + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-project + 4.0.0-SNAPSHOT + + + helidon-integrations-eclipsestore + Helidon Integrations Eclipse Store + jar + + + + org.eclipse.store + storage-embedded + + + org.eclipse.store + storage-embedded-configuration + + + org.eclipse.serializer + persistence-binary-jdk17 + + + io.helidon.config + helidon-config + + + io.helidon.common.features + helidon-common-features-api + true + + + io.helidon.config + helidon-config-yaml + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.helidon.common.features + helidon-common-features-processor + ${helidon.version} + + + + + + + diff --git a/integrations/eclipsestore/core/src/main/java/io/helidon/integrations/eclipsestore/core/EmbeddedStorageManagerBuilder.java b/integrations/eclipsestore/core/src/main/java/io/helidon/integrations/eclipsestore/core/EmbeddedStorageManagerBuilder.java new file mode 100644 index 00000000000..7a11486f29b --- /dev/null +++ b/integrations/eclipsestore/core/src/main/java/io/helidon/integrations/eclipsestore/core/EmbeddedStorageManagerBuilder.java @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.eclipsestore.core; + +import java.time.Duration; +import java.util.Map; + +import io.helidon.config.Config; + +import org.eclipse.serializer.configuration.types.ByteSize; +import org.eclipse.serializer.persistence.binary.jdk17.types.BinaryHandlersJDK17; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageFoundationCreatorConfigurationBased; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.StorageEntityCacheEvaluator; + + + +/** + * Builder for Eclipse Store EmbeddedStorageManager. + */ +public class EmbeddedStorageManagerBuilder implements io.helidon.common.Builder { + private final EmbeddedStorageConfigurationBuilder configurationBuilder; + + private EmbeddedStorageManagerBuilder() { + super(); + configurationBuilder = EmbeddedStorageConfigurationBuilder.New(); + } + + /** + * A builder for the EmbeddedStorageManager. + * + * @return a new fluent API builder + */ + public static EmbeddedStorageManagerBuilder builder() { + return new EmbeddedStorageManagerBuilder(); + } + + /** + * Create a EmbeddedStorageManager instance from Config. + * + * @param config configuration to use + * @return new EmbeddedStorageManager instance + */ + public static EmbeddedStorageManager create(Config config) { + return builder().config(config).build(); + } + + @Override + public EmbeddedStorageManager build() { + var embeddedStorageFoundation = EmbeddedStorageFoundationCreatorConfigurationBased + .New(configurationBuilder + .buildConfiguration()) + .createEmbeddedStorageFoundation(); + embeddedStorageFoundation.onConnectionFoundation(BinaryHandlersJDK17::registerJDK17TypeHandlers); + return embeddedStorageFoundation.createEmbeddedStorageManager(); + } + + /** + * Update builder from configuration. + * + * @param config + * @return the fluent API builder + */ + public EmbeddedStorageManagerBuilder config(Config config) { + Map configMap = config.detach().asMap().get(); + + configMap.forEach(configurationBuilder::set); + + return this; + } + + /** + * The base directory of the storage in the file system. + * + * @param storageDirectory location of the storage as string. + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder storageDirectory(String storageDirectory) { + configurationBuilder.setStorageDirectory(storageDirectory); + return this; + } + + /** + * The backup directory. + * + * @param backupDirectory location of the storage backup as string. + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder backupDirectory(String backupDirectory) { + configurationBuilder.setBackupDirectory(backupDirectory); + return this; + } + + /** + * The deletion directory. + * + * @param deletionDirectory location of the storage's deleted files backup as string. + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder deletionDirectory(String deletionDirectory) { + configurationBuilder.setDeletionDirectory(deletionDirectory); + return this; + } + + /** + * The truncation directory. + * + * @param truncationDirectory location of the storage's truncation files backup as string. + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder truncationDirectory(String truncationDirectory) { + configurationBuilder.setDeletionDirectory(truncationDirectory); + return this; + } + + /** + * Name prefix of the subdirectories used by the channel threads. Default is + * "channel_". + * + * @param channelDirectoryPrefix new prefix + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder channelDirectoryPrefix(String channelDirectoryPrefix) { + configurationBuilder.setChannelDirectoryPrefix(channelDirectoryPrefix); + return this; + } + + /** + * Name prefix of the storage files. Default is "channel_". + * + * @param dataFilePrefix new prefix + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder dataFilePrefix(String dataFilePrefix) { + configurationBuilder.setDataFilePrefix(dataFilePrefix); + return this; + } + + /** + * Name suffix of the storage files. Default is ".dat". + * + * @param dataFileSuffix new suffix + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder dataFileSuffix(String dataFileSuffix) { + configurationBuilder.setDataFileSuffix(dataFileSuffix); + return this; + } + + /** + * Name prefix of the storage transaction file. Default is + * "transactions_". + * + * @param transactionFilePrefix new prefix + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder transactionFilePrefix(String transactionFilePrefix) { + configurationBuilder.setTransactionFilePrefix(transactionFilePrefix); + return this; + } + + /** + * Name suffix of the storage transaction file. Default is ".sft". + * + * @param transactionFileSuffix new suffix + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder transactionFileSuffix(String transactionFileSuffix) { + configurationBuilder.setTransactionFileSuffix(transactionFileSuffix); + return this; + } + + /** + * The name of the dictionary file. Default is + * "PersistenceTypeDictionary.ptd". + * + * @param typeDictionaryFilename new name + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder typeDictionaryFilename(String typeDictionaryFilename) { + configurationBuilder.setTypeDictionaryFileName(typeDictionaryFilename); + return this; + } + + /** + * Name suffix of the storage rescue files. Default is ".bak". + * + * @param rescuedFileSuffix + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder rescuedFileSuffix(String rescuedFileSuffix) { + configurationBuilder.setRescuedFileSuffix(rescuedFileSuffix); + return this; + } + + /** + * Name of the lock file. Default is "used.lock" + * + * @param lockFileName + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder lockFileName(String lockFileName) { + configurationBuilder.setLockFileName(lockFileName); + return this; + } + + /** + * The number of threads and number of directories used by the storage engine. + * Every thread has exclusive access to its directory. Default is + * 1. + * + * @param channelCount the new channel count, must be a power of 2 + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder channelCount(int channelCount) { + configurationBuilder.setChannelCount(channelCount); + return this; + } + + /** + * Interval in milliseconds for the housekeeping. This is work like garbage + * collection or cache checking. In combination with + * {@link #housekeepingTimeBudget(Duration)} the maximum processor time for + * housekeeping work can be set. Default is 1000 (every second). + * + * @param housekeepingInterval the new interval + * @return EmbeddedStorageManager builder + * @see #housekeepingTimeBudget(Duration) + */ + public EmbeddedStorageManagerBuilder housekeepingInterval(Duration housekeepingInterval) { + configurationBuilder.setHousekeepingInterval(housekeepingInterval); + return this; + } + + /** + * Number of nanoseconds used for each housekeeping cycle. However, no matter + * how low the number is, one item of work will always be completed. But if + * there is nothing to clean up, no processor time will be wasted. Default is + * 10000000 (10 million nanoseconds = 10 milliseconds = 0.01 + * seconds). + * + * @param housekeepingTimeBudget the new time budget + * @return EmbeddedStorageManager builder + * @see #housekeepingInterval(Duration) + */ + public EmbeddedStorageManagerBuilder housekeepingTimeBudget(Duration housekeepingTimeBudget) { + configurationBuilder.setHousekeepingTimeBudget(housekeepingTimeBudget); + return this; + } + + /** + * Timeout in milliseconds for the entity cache evaluator. If an entity wasn't + * accessed in this timespan it will be removed from the cache. Default is + * 86400000 (1 day). + * + * @param entityCacheTimeout + * @return EmbeddedStorageManager builder + * @see Duration + */ + public EmbeddedStorageManagerBuilder entityCacheTimeout(Duration entityCacheTimeout) { + configurationBuilder.setEntityCacheTimeout(entityCacheTimeout); + return this; + } + + /** + * Abstract threshold value for the lifetime of entities in the cache. See + * {@link StorageEntityCacheEvaluator}. Default is 1000000000. + * + * @param entityCacheThreshold the new threshold + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder entityCacheThreshold(long entityCacheThreshold) { + configurationBuilder.setEntityCacheThreshold(entityCacheThreshold); + return this; + } + + /** + * Minimum file size for a data file to avoid cleaning it up. Default is 1024^2 + * = 1 MiB. + * + * @param dataFileMinimumSize the new minimum file size + * @return EmbeddedStorageManager builder + * @see #dataFileMinimumUseRatio(double) + */ + public EmbeddedStorageManagerBuilder dataFileMinimumSize(ByteSize dataFileMinimumSize) { + configurationBuilder.setDataFileMinimumSize(dataFileMinimumSize); + return this; + } + + /** + * Maximum file size for a data file to avoid cleaning it up. Default is + * 1024^2*8 = 8 MiB. + * + * @param dataFileMaximumSize the new maximum file size + * @return EmbeddedStorageManager builder + * @see #dataFileMinimumUseRatio(double) + */ + public EmbeddedStorageManagerBuilder dataFileMaximumSize(ByteSize dataFileMaximumSize) { + configurationBuilder.setDataFileMaximumSize(dataFileMaximumSize); + return this; + } + + /** + * The ratio (value in ]0.0;1.0]) of non-gap data contained in a storage file to + * prevent the file from being dissolved. "Gap" data is anything that is not the + * latest version of an entity's data, including older versions of an entity and + * "comment" bytes (a sequence of bytes beginning with its length as a negative + * value length header).
+ * The closer this value is to 1.0 (100%), the less disk space is occupied by + * storage files, but the more file dissolving (data transfers to new files) is + * required and vice versa. + * + * @param dataFileMinimumUseRatio the new minimum use ratio + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder dataFileMinimumUseRatio(double dataFileMinimumUseRatio) { + configurationBuilder.setDataFileMinimumUseRatio(dataFileMinimumUseRatio); + return this; + } + + /** + * A flag defining whether the current head file (the only file actively written + * to) shall be subjected to file cleanups as well. + * + * @param dataFileCleanupHeadFile + * @return EmbeddedStorageManager builder + */ + public EmbeddedStorageManagerBuilder dataFileCleanupHeadFile(boolean dataFileCleanupHeadFile) { + configurationBuilder.setDataFileCleanupHeadFile(dataFileCleanupHeadFile); + return this; + } + +} diff --git a/integrations/eclipsestore/core/src/main/java/io/helidon/integrations/eclipsestore/core/package-info.java b/integrations/eclipsestore/core/src/main/java/io/helidon/integrations/eclipsestore/core/package-info.java new file mode 100644 index 00000000000..06057aabe81 --- /dev/null +++ b/integrations/eclipsestore/core/src/main/java/io/helidon/integrations/eclipsestore/core/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides support for Eclipse Store core features integration. + */ +package io.helidon.integrations.eclipsestore.core; diff --git a/integrations/eclipsestore/core/src/main/java/module-info.java b/integrations/eclipsestore/core/src/main/java/module-info.java new file mode 100644 index 00000000000..db7f15cfda0 --- /dev/null +++ b/integrations/eclipsestore/core/src/main/java/module-info.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import io.helidon.common.features.api.Aot; +import io.helidon.common.features.api.Feature; +import io.helidon.common.features.api.HelidonFlavor; + +/** + * Provides support for Eclipse Store core features integration. + */ +@Feature(value = "Eclipse Store", + description = "Eclipse Store Integration", + in = HelidonFlavor.SE, + path = "EclipseStore") +@Aot(false) +module io.helidon.integrations.eclipsestore { + requires transitive static io.helidon.common.features.api; + requires transitive io.helidon.config; + requires transitive io.helidon.common; + + requires transitive org.eclipse.store.storage.embedded.configuration; + requires transitive org.eclipse.serializer.persistence.binary.jdk17; + + exports io.helidon.integrations.eclipsestore.core; +} diff --git a/integrations/eclipsestore/core/src/main/resources/META-INF/native-image/reflect-config.json b/integrations/eclipsestore/core/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 00000000000..b0e2808cf58 --- /dev/null +++ b/integrations/eclipsestore/core/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,548 @@ +[ + { + "name": "org.eclipse.store.afs.nio.types.NioReadableFile$Default[]" + }, + { + "name": "org.eclipse.store.memory.sun.JdkInternals$ObjectHeaderSizeDummy", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "fields": [ + { + "name": "calculateByteSizeObjectHeaderFieldOffsetDummy", + "allowUnsafeAccess": true + } + ] + }, + { + "name": "java.net.URL", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "boolean", + "allDeclaredMethods": true + }, + { + "name": "byte", + "allDeclaredMethods": true + }, + { + "name": "char", + "allDeclaredMethods": true + }, + { + "name": "double", + "allDeclaredMethods": true + }, + { + "name": "float", + "allDeclaredMethods": true + }, + { + "name": "int", + "allDeclaredMethods": true + }, + { + "name": "java.io.File", + "allDeclaredMethods": true + }, + { + "name": "java.lang.Boolean", + "allDeclaredMethods": true + }, + { + "name": "java.lang.Byte", + "allDeclaredMethods": true + }, + { + "name": "java.lang.Character", + "allDeclaredMethods": true + }, + { + "name": "java.lang.Class", + "allDeclaredMethods": true + }, + { + "name": "java.lang.Double", + "allDeclaredMethods": true + }, + { + "name": "java.lang.Float", + "allDeclaredMethods": true + }, + { + "name": "java.lang.Integer", + "allDeclaredMethods": true + }, + { + "name": "java.lang.Long", + "allDeclaredMethods": true + }, + { + "name": "java.lang.Object", + "allDeclaredMethods": true + }, + { + "name": "java.lang.Object[]", + "allDeclaredMethods": true + }, + { + "name": "java.lang.Short", + "allDeclaredMethods": true + }, + { + "name": "java.lang.String", + "allDeclaredMethods": true + }, + { + "name": "java.lang.StringBuffer", + "allDeclaredMethods": true + }, + { + "name": "java.lang.StringBuilder", + "allDeclaredMethods": true + }, + { + "name": "java.lang.String[]" + }, + { + "name": "java.lang.Void", + "allDeclaredMethods": true + }, + { + "name": "java.lang.ref.WeakReference[]" + }, + { + "name": "java.lang.reflect.Field[]" + }, + { + "name": "java.math.BigDecimal", + "allDeclaredMethods": true + }, + { + "name": "java.math.BigInteger", + "allDeclaredMethods": true + }, + { + "name": "java.net.Inet4Address", + "allDeclaredMethods": true + }, + { + "name": "java.net.Inet6Address", + "allDeclaredMethods": true + }, + { + "name": "java.net.InetAddress", + "allDeclaredMethods": true + }, + { + "name": "java.net.InetSocketAddress", + "allDeclaredMethods": true + }, + { + "name": "java.net.URI", + "allDeclaredMethods": true + }, + { + "name": "java.net.URL", + "allDeclaredMethods": true + }, + { + "name": "java.nio.file.OpenOption[]" + }, + { + "name": "java.nio.file.Path", + "allDeclaredMethods": true + }, + { + "name": "java.sql.Date", + "allDeclaredMethods": true + }, + { + "name": "java.sql.Time", + "allDeclaredMethods": true + }, + { + "name": "java.sql.Timestamp", + "allDeclaredMethods": true + }, + { + "name": "java.util.AbstractCollection", + "allDeclaredFields": true + }, + { + "name": "java.util.AbstractList", + "allDeclaredFields": true + }, + { + "name": "java.util.AbstractMap", + "allDeclaredFields": true + }, + { + "name": "java.util.AbstractSet", + "allDeclaredFields": true + }, + { + "name": "java.util.ArrayDeque", + "allDeclaredMethods": true + }, + { + "name": "java.util.ArrayList", + "allDeclaredMethods": true + }, + { + "name": "java.util.Collections$EmptyList", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "java.util.Collections$EmptyMap", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "java.util.Collections$EmptySet", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "java.util.Collections$ReverseComparator", + "allDeclaredMethods": true + }, + { + "name": "java.util.Collections$UnmodifiableNavigableMap$EmptyNavigableMap", + "allDeclaredMethods": true + }, + { + "name": "java.util.Collections$UnmodifiableNavigableSet$EmptyNavigableSet", + "allDeclaredMethods": true + }, + { + "name": "java.util.Comparator" + }, + { + "name": "java.util.Comparators$NaturalOrderComparator", + "allDeclaredMethods": true + }, + { + "name": "java.util.Currency", + "allDeclaredMethods": true + }, + { + "name": "java.util.Date", + "allDeclaredMethods": true + }, + { + "name": "java.util.HashMap", + "allDeclaredMethods": true + }, + { + "name": "java.util.HashSet", + "allDeclaredMethods": true + }, + { + "name": "java.util.Hashtable", + "allDeclaredMethods": true + }, + { + "name": "java.util.IdentityHashMap", + "allDeclaredMethods": true + }, + { + "name": "java.util.LinkedHashMap", + "allDeclaredMethods": true + }, + { + "name": "java.util.LinkedHashSet", + "allDeclaredMethods": true + }, + { + "name": "java.util.LinkedList", + "allDeclaredMethods": true + }, + { + "name": "java.util.List", + "allDeclaredMethods": true + }, + { + "name": "java.util.Locale", + "allDeclaredMethods": true + }, + { + "name": "java.util.Optional", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "fields": [ + { + "name": "value", + "allowWrite": true + } + ] + }, + { + "name": "java.util.OptionalDouble", + "allDeclaredMethods": true + }, + { + "name": "java.util.OptionalInt", + "allDeclaredMethods": true + }, + { + "name": "java.util.OptionalLong", + "allDeclaredMethods": true + }, + { + "name": "java.util.PriorityQueue", + "allDeclaredMethods": true + }, + { + "name": "java.util.Properties", + "allDeclaredMethods": true + }, + { + "name": "java.util.Stack", + "allDeclaredMethods": true + }, + { + "name": "java.util.TreeMap", + "allDeclaredMethods": true + }, + { + "name": "java.util.TreeSet", + "allDeclaredMethods": true + }, + { + "name": "java.util.Vector", + "allDeclaredMethods": true + }, + { + "name": "java.util.WeakHashMap", + "allDeclaredMethods": true + }, + { + "name": "java.util.concurrent.ConcurrentHashMap", + "allDeclaredMethods": true + }, + { + "name": "java.util.concurrent.ConcurrentLinkedDeque", + "allDeclaredMethods": true + }, + { + "name": "java.util.concurrent.ConcurrentLinkedQueue", + "allDeclaredMethods": true + }, + { + "name": "java.util.concurrent.ConcurrentSkipListMap", + "allDeclaredMethods": true + }, + { + "name": "java.util.concurrent.ConcurrentSkipListSet", + "allDeclaredMethods": true + }, + { + "name": "java.util.concurrent.CopyOnWriteArrayList", + "allDeclaredMethods": true + }, + { + "name": "java.util.regex.Pattern", + "allDeclaredMethods": true + }, + { + "name": "long", + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store..collections.BulkList", + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.ConstHashEnum", + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.ConstHashTable", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.ConstHashTable$Keys" + }, + { + "name": "org.eclipse.store.collections.ConstHashTable$Values" + }, + { + "name": "org.eclipse.store.collections.ConstList", + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.Empty", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.EmptyTable", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "fields": [ + { + "name": "keys", + "allowWrite": true + }, + { + "name": "values", + "allowWrite": true + } + ] + }, + { + "name": "org.eclipse.store.collections.EmptyTable$Keys", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "fields": [ + { + "name": "this$0", + "allowWrite": true + } + ] + }, + { + "name": "org.eclipse.store.collections.EmptyTable$Values", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "fields": [ + { + "name": "this$0", + "allowWrite": true + } + ] + }, + { + "name": "org.eclipse.store.collections.EqBulkList", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.EqConstHashEnum", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.EqConstHashTable", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.EqConstHashTable$Keys" + }, + { + "name": "org.eclipse.store.collections.EqConstHashTable$Values" + }, + { + "name": "org.eclipse.store.collections.EqHashEnum", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.EqHashTable", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.EqHashTable$Keys" + }, + { + "name": "org.eclipse.store.collections.EqHashTable$Values" + }, + { + "name": "org.eclipse.store.collections.FixedList", + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.HashEnum", + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.HashTable", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.HashTable$Keys" + }, + { + "name": "org.eclipse.store.collections.HashTable$Values" + }, + { + "name": "org.eclipse.store.collections.LimitList", + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.collections.Singleton", + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.hashing.HashEqualator" + }, + { + "name": "org.eclipse.store.hashing.XHashing$SingletonIdentityHashEqualator", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.hashing.XHashing$SingletonKeyValueIdentityHashEqualator", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.hashing.XHashing$SingletonValueHashEqualator", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.persistence.binary.java.net.BinaryHandlerInetSocketAddress", + "allDeclaredFields": true + }, + { + "name": "org.eclipse.store.persistence.binary.types.BinaryField[]" + }, + { + "name": "org.eclipse.store.persistence.binary.types.BinaryReferenceTraverser[]" + }, + { + "name": "org.eclipse.store.persistence.binary.types.LoadItemsChain$Entry[]" + }, + { + "name": "org.eclipse.store.persistence.types.PersistenceRootReference$Default", + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.persistence.types.PersistenceRoots$Default", + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.persistence.types.PersistenceTypeDefinitionMemberFieldGeneric[]" + }, + { + "name": "org.eclipse.store.persistence.types.PersistenceTypeDescription[]" + }, + { + "name": "org.eclipse.store.reference.Lazy$Default", + "allDeclaredMethods": true + }, + { + "name": "org.eclipse.store.util.Substituter$Default", + "allDeclaredMethods": true + }, + { + "name": "short", + "allDeclaredMethods": true + }, + { + "name": "sun.misc.Unsafe", + "fields": [ + { + "name": "theUnsafe" + } + ] + } +] diff --git a/integrations/eclipsestore/core/src/test/java/io/helidon/integrations/eclipsestore/core/ConfigurationTest.java b/integrations/eclipsestore/core/src/test/java/io/helidon/integrations/eclipsestore/core/ConfigurationTest.java new file mode 100644 index 00000000000..68eede4767d --- /dev/null +++ b/integrations/eclipsestore/core/src/test/java/io/helidon/integrations/eclipsestore/core/ConfigurationTest.java @@ -0,0 +1,60 @@ +/* + * 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.integrations.eclipsestore.core; + +import io.helidon.config.ClasspathConfigSource; +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.text.StringContainsInOrder.stringContainsInOrder; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class ConfigurationTest { + + @TempDir + Path tempDir; + + @Test + void createFromConfigTest() { + + Config helidonConfig = Config.builder().addSource(ClasspathConfigSource.create("/eclipsestoreConfig.yml")) + .addSource(ConfigSources.create(Map.of("eclipsestore.storage-directory", tempDir.toString()))) + .build(); + + EmbeddedStorageManagerBuilder embeddedStorageManagerBuilder = EmbeddedStorageManagerBuilder.builder(); + EmbeddedStorageManager embeddedStorageManager = embeddedStorageManagerBuilder.config(helidonConfig.get("eclipsestore")) + .build(); + + assertNotNull(embeddedStorageManager, "embeddedStorageManager is null!"); + + //need to compare strings as the eclipse store Path is not a java path + assertThat(tempDir.toString(), stringContainsInOrder( + Arrays.asList(embeddedStorageManager.configuration().fileProvider().baseDirectory().toPath()))); + + assertThat(embeddedStorageManager.configuration().channelCountProvider().getChannelCount(), is(4)); + assertThat(embeddedStorageManager.configuration().housekeepingController().housekeepingIntervalMs(), is(2000L)); + } +} diff --git a/integrations/eclipsestore/core/src/test/resources/eclipsestoreConfig.yml b/integrations/eclipsestore/core/src/test/resources/eclipsestoreConfig.yml new file mode 100644 index 00000000000..c4b6be3caa9 --- /dev/null +++ b/integrations/eclipsestore/core/src/test/resources/eclipsestoreConfig.yml @@ -0,0 +1,23 @@ +# +# Copyright (c) 2021, 2024 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +openapi: + web-context: /otheropenapi + port: 0 + +eclipsestore: + channel-count: 4 + housekeeping-interval: 2000ms diff --git a/integrations/eclipsestore/health/pom.xml b/integrations/eclipsestore/health/pom.xml new file mode 100644 index 00000000000..04c939ab538 --- /dev/null +++ b/integrations/eclipsestore/health/pom.xml @@ -0,0 +1,60 @@ + + + + 4.0.0 + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-project + 4.0.0-SNAPSHOT + + + helidon-integrations-eclipsestore-health + Helidon Integrations Eclipse Store Health + + Health check for Eclipse Store + + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore + + + io.helidon.webserver.observe + helidon-webserver-observe-health + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + org.mockito + mockito-core + test + + + + diff --git a/integrations/eclipsestore/health/src/main/java/io/helidon/integrations/eclipsestore/health/EclipseStoreHealthCheck.java b/integrations/eclipsestore/health/src/main/java/io/helidon/integrations/eclipsestore/health/EclipseStoreHealthCheck.java new file mode 100644 index 00000000000..670d7af0be1 --- /dev/null +++ b/integrations/eclipsestore/health/src/main/java/io/helidon/integrations/eclipsestore/health/EclipseStoreHealthCheck.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.eclipsestore.health; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import io.helidon.health.HealthCheck; +import io.helidon.health.HealthCheckResponse; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; + + +/** + * Eclipse Store health check. + */ +public class EclipseStoreHealthCheck implements HealthCheck { + + private static final String DEFAULT_NAME = "EclipseStore"; + private static final long DEFAULT_TIMEOUT_SECONDS = 10; + + private final EmbeddedStorageManager embeddedStorageManager; + private final long timeoutDuration; + private final TimeUnit timeoutUnit; + private final String name; + + private EclipseStoreHealthCheck(Builder builder) { + this.embeddedStorageManager = builder.embeddedStorageManager; + this.timeoutDuration = builder.timeoutDuration; + this.timeoutUnit = builder.timeoutUnit; + this.name = builder.name; + } + + /** + * Create a default health check for Eclipse Store. + * + * @param embeddedStorageManager the EmbeddedStorageManager used by the the health check + * @return default health check for Eclipse Store. + */ + public static EclipseStoreHealthCheck create(EmbeddedStorageManager embeddedStorageManager) { + return builder(embeddedStorageManager).build(); + } + + /** + * A fluent API builder to create a health check for Eclipse Store. + * + * @param embeddedStorageManager EmbeddedStorageManager + * @return a new builder + */ + public static Builder builder(EmbeddedStorageManager embeddedStorageManager) { + return new Builder(embeddedStorageManager); + } + + @Override + public String name() { + return name; + } + + @Override + public HealthCheckResponse call() { + HealthCheckResponse.Builder builder = HealthCheckResponse.builder(); + + try { + CompletableFuture status = CompletableFuture.supplyAsync(embeddedStorageManager::isRunning) + .orTimeout(timeoutDuration, timeoutUnit); + + builder.status(status.get()); + } catch (Throwable e) { + builder.status(false) + .detail("ErrorMessage", e.getMessage()) + .detail("ErrorClass", e.getClass().getName()); + } + + return builder.build(); + } + + /** + * Builder for EclipseStoreHealthCheck. + */ + public static class Builder implements io.helidon.common.Builder { + + private final EmbeddedStorageManager embeddedStorageManager; + + private long timeoutDuration; + private TimeUnit timeoutUnit; + private String name; + + private Builder(EmbeddedStorageManager embeddedStorageManager) { + this.embeddedStorageManager = embeddedStorageManager; + this.name = DEFAULT_NAME; + this.timeoutDuration = DEFAULT_TIMEOUT_SECONDS; + this.timeoutUnit = TimeUnit.SECONDS; + } + + @Override + public EclipseStoreHealthCheck build() { + return new EclipseStoreHealthCheck(this); + } + + /** + * Customized name of the health check. + * + * @param name name of the health check + * @return updated builder instance + */ + public Builder name(String name) { + this.name = name; + return this; + } + + /** + * Set custom timeout to wait for statement execution response. Default value is + * 10 seconds. + * + * @param duration the maximum time to wait for statement execution response + * @param timeUnit the time unit of the timeout argument + * @return updated builder instance + * @deprecated use {@link #timeout(Duration)} instead + */ + @Deprecated(since = "4.0.0", forRemoval = true) + public Builder timeout(long duration, TimeUnit timeUnit) { + this.timeoutDuration = duration; + this.timeoutUnit = timeUnit; + return this; + } + + /** + * Set custom timeout to wait for statement execution response. Default value is + * 10 seconds. + * + * @param duration the maximum time to wait for statement execution response + * @return updated builder instance + */ + public Builder timeout(Duration duration) { + this.timeoutDuration = duration.toNanos(); + this.timeoutUnit = TimeUnit.NANOSECONDS; + return this; + } + } +} diff --git a/integrations/eclipsestore/health/src/main/java/io/helidon/integrations/eclipsestore/health/package-info.java b/integrations/eclipsestore/health/src/main/java/io/helidon/integrations/eclipsestore/health/package-info.java new file mode 100644 index 00000000000..051295f1fc0 --- /dev/null +++ b/integrations/eclipsestore/health/src/main/java/io/helidon/integrations/eclipsestore/health/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides support for Eclipse store HealthChecks features integration. + */ +package io.helidon.integrations.eclipsestore.health; diff --git a/integrations/eclipsestore/health/src/main/java/module-info.java b/integrations/eclipsestore/health/src/main/java/module-info.java new file mode 100644 index 00000000000..0325c979d91 --- /dev/null +++ b/integrations/eclipsestore/health/src/main/java/module-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import io.helidon.common.features.api.Aot; +import io.helidon.common.features.api.Feature; +import io.helidon.common.features.api.HelidonFlavor; + +/** + * Provides support for MEclipse Store HealthChecks features integration. + */ +@Feature(value = "Eclipse Store Health", + description = "Eclipse Store Health Integration", + in = HelidonFlavor.SE, + path = {"EclipseStore", "Health"} +) +@Aot(false) +module io.helidon.integrations.eclipsestore.health { + requires transitive io.helidon.health; + requires transitive io.helidon.integrations.eclipsestore; + + exports io.helidon.integrations.eclipsestore.health; + +} diff --git a/integrations/eclipsestore/health/src/test/java/io/helidon/integrations/eclipsestore/health/EclipseStoreHealthTest.java b/integrations/eclipsestore/health/src/test/java/io/helidon/integrations/eclipsestore/health/EclipseStoreHealthTest.java new file mode 100644 index 00000000000..661da9432b9 --- /dev/null +++ b/integrations/eclipsestore/health/src/test/java/io/helidon/integrations/eclipsestore/health/EclipseStoreHealthTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.eclipsestore.health; + +import io.helidon.health.HealthCheckResponse; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class EclipseStoreHealthTest { + private EmbeddedStorageManager embeddedStorageManager; + + @BeforeEach + void init() { + embeddedStorageManager = Mockito.mock(EmbeddedStorageManager.class); + } + + private void setEclipseStoreStatus(boolean isRunning) { + Mockito.when(embeddedStorageManager.isRunning()).thenReturn(isRunning); + } + + @Test + void testStatusRunning() { + setEclipseStoreStatus(true); + EclipseStoreHealthCheck check = EclipseStoreHealthCheck.create(embeddedStorageManager); + HealthCheckResponse response = check.call(); + assertThat(response.status(), is(HealthCheckResponse.Status.UP)); + } + + @Test + void testStatusNotRunning() { + setEclipseStoreStatus(false); + EclipseStoreHealthCheck check = EclipseStoreHealthCheck.create(embeddedStorageManager); + HealthCheckResponse response = check.call(); + assertThat(response.status(), is(HealthCheckResponse.Status.DOWN)); + } + + @Test + void testStatusTimeout() { + + Mockito.when(embeddedStorageManager.isRunning()).then((x) -> { + Thread.sleep(100); + return true; + }); + + EclipseStoreHealthCheck check = EclipseStoreHealthCheck + .builder(embeddedStorageManager) + .timeout(Duration.of(20, ChronoUnit.MILLIS)) + .build(); + + HealthCheckResponse response = check.call(); + assertThat(response.status(), is(HealthCheckResponse.Status.DOWN)); + } +} diff --git a/integrations/eclipsestore/metrics/pom.xml b/integrations/eclipsestore/metrics/pom.xml new file mode 100644 index 00000000000..1ca8454cde6 --- /dev/null +++ b/integrations/eclipsestore/metrics/pom.xml @@ -0,0 +1,69 @@ + + + + 4.0.0 + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-project + 4.0.0-SNAPSHOT + + + helidon-integrations-eclipsestore-metrics + Helidon Integrations Eclipse Stream Metrics + jar + + Metrics support for Eclipse Stream + + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore + + + io.helidon.metrics + helidon-metrics-api + + + io.helidon.config + helidon-config + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + org.mockito + mockito-core + test + + + io.helidon.common.features + helidon-common-features-api + + + + diff --git a/integrations/eclipsestore/metrics/src/main/java/io/helidon/integrations/eclipsestore/metrics/EclipseStoreHealthMetricsSupport.java b/integrations/eclipsestore/metrics/src/main/java/io/helidon/integrations/eclipsestore/metrics/EclipseStoreHealthMetricsSupport.java new file mode 100644 index 00000000000..2472e32f386 --- /dev/null +++ b/integrations/eclipsestore/metrics/src/main/java/io/helidon/integrations/eclipsestore/metrics/EclipseStoreHealthMetricsSupport.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.eclipsestore.metrics; + +import java.util.List; +import java.util.Objects; +import java.util.function.ToDoubleFunction; + +import io.helidon.common.config.Config; +import io.helidon.metrics.api.Gauge; +import io.helidon.metrics.api.MeterRegistry; +import io.helidon.metrics.api.MetricsConfig; +import io.helidon.metrics.api.MetricsFactory; +import io.helidon.metrics.api.Tag; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.StorageRawFileStatistics; + +import static io.helidon.metrics.api.Meter.BaseUnits.BYTES; + +/** + * Helper class that provides the default metrics for an Eclipse Store EmbeddedStorageManager. + */ +public class EclipseStoreHealthMetricsSupport { + + private static final String CONFIG_METRIC_ENABLED_VENDOR = "vendor."; + + private static final GaugeInfo GLOBAL_FILE_COUNT = + new GaugeInfo<>("eclipsestore.globalFileCount", + "Displays the number of storage files.", + null, + StorageRawFileStatistics::fileCount); + + private static final GaugeInfo LIVE_DATA_LENGTH = + new GaugeInfo<>("eclipsestore.liveDataLength", + "Displays live data length. This is the 'real' size of the stored data.", + BYTES, + StorageRawFileStatistics::liveDataLength); + + private static final GaugeInfo TOTAL_DATA_LENGTH = + new GaugeInfo<>("eclipsestore.totalDataLength", + "Displays total data length. This is the accumulated size of all storage data files.", + BYTES, + StorageRawFileStatistics::totalDataLength); + + private final Config config; + private final EmbeddedStorageManager embeddedStorageManager; + private final MeterRegistry vendorRegistry; + + private EclipseStoreHealthMetricsSupport(Builder builder) { + super(); + this.config = builder.config(); + this.embeddedStorageManager = builder.embeddedStorageManager(); + + MetricsFactory metricsFactory; + if (builder.metricsFactory() == null) { + metricsFactory = MetricsFactory.getInstance(config.get(MetricsConfig.METRICS_CONFIG_KEY)); + } else { + metricsFactory = builder.metricsFactory(); + } + + this.vendorRegistry = metricsFactory.globalRegistry(); + } + + /** + * Create a new builder to construct an instance. + * + * @param embeddedStorageManager EmbeddedStorageManager instance that supplies the metrics data. + * @return A new builder instance + */ + public static Builder builder(EmbeddedStorageManager embeddedStorageManager) { + return new Builder(embeddedStorageManager); + } + + private void register(GaugeInfo gaugeInfo, StorageRawFileStatistics stats) { + if (config.get(CONFIG_METRIC_ENABLED_VENDOR + gaugeInfo.name + ".enabled") + .asBoolean() + .orElse(true)) { + vendorRegistry.getOrCreate(gaugeInfo.builder(stats)); + } + } + + /** + * Register this metrics at the vendor metrics registry. + */ + public void registerMetrics() { + StorageRawFileStatistics stats = embeddedStorageManager.createStorageStatistics(); + register(GLOBAL_FILE_COUNT, stats); + register(LIVE_DATA_LENGTH, stats); + register(TOTAL_DATA_LENGTH, stats); + } + + private record GaugeInfo(String name, + String description, + String unit, + ToDoubleFunction fn, + Tag... tags) { + + Gauge.Builder builder(T stateObject) { + Gauge.Builder builder = Gauge.builder(name, stateObject, fn) + .description(description); + if (unit != null) { + builder.baseUnit(unit); + } + if (tags != null) { + builder.tags(List.of(tags)); + } + return builder; + } + } + + /** + * A fluent API builder to build instances of {@link io.helidon.integrations.eclipsestore.metrics.EclipseStoreHealthMetricsSupport}. + */ + public static final class Builder implements io.helidon.common.Builder { + + private final EmbeddedStorageManager embeddedStorageManager; + private Config config = Config.empty(); + private MetricsFactory metricsFactory; + + private Builder(EmbeddedStorageManager embeddedStorageManager) { + Objects.requireNonNull(embeddedStorageManager); + this.embeddedStorageManager = embeddedStorageManager; + } + + @Override + public EclipseStoreHealthMetricsSupport build() { + return new EclipseStoreHealthMetricsSupport(this); + } + + /** + * get the current configured MetricsFactory. + * + * @return MetricsFactory + */ + public MetricsFactory metricsFactory() { + return this.metricsFactory; + } + + /** + * get the current configuredEmbeddedStorageManager. + * + * @return EmbeddedStorageManager + */ + public EmbeddedStorageManager embeddedStorageManager() { + return this.embeddedStorageManager; + } + + /** + * get the current configured helidon configuration. + * + * @return Config + */ + public Config config() { + return this.config; + } + + /** + * set the MetricsFactory. + * + * @param metricsFactory metrics factory + * @return EclipseStoreHealthMetricsSupport builder + */ + public Builder metricsFactory(MetricsFactory metricsFactory) { + this.metricsFactory = metricsFactory; + return this; + } + + /** + * set the helidon configuration used by the builder. + * + * @param config configuration + * @return EclipseStoreHealthMetricsSupport builder + */ + public Builder config(Config config) { + this.config = config; + return this; + } + } +} diff --git a/integrations/eclipsestore/metrics/src/main/java/io/helidon/integrations/eclipsestore/metrics/package-info.java b/integrations/eclipsestore/metrics/src/main/java/io/helidon/integrations/eclipsestore/metrics/package-info.java new file mode 100644 index 00000000000..51b75eae2e5 --- /dev/null +++ b/integrations/eclipsestore/metrics/src/main/java/io/helidon/integrations/eclipsestore/metrics/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides support for Eclipse Store Metrics features integration. + */ +package io.helidon.integrations.eclipsestore.metrics; diff --git a/integrations/eclipsestore/metrics/src/main/java/module-info.java b/integrations/eclipsestore/metrics/src/main/java/module-info.java new file mode 100644 index 00000000000..b6ffa63d38d --- /dev/null +++ b/integrations/eclipsestore/metrics/src/main/java/module-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import io.helidon.common.features.api.Aot; +import io.helidon.common.features.api.Feature; +import io.helidon.common.features.api.HelidonFlavor; +/** + * Provides support for Eclipse Store Metrics features integration. + */ +@Feature(value = "Eclipse Store Metrics", + description = "Eclipse Store Metrics Integration", + in = HelidonFlavor.SE, + path = {"EclipseStore", "Metrics"} +) +@Aot(false) +module io.helidon.integrations.eclipsestore.metrics { + requires transitive io.helidon.metrics.api; + + requires transitive io.helidon.integrations.eclipsestore; + + exports io.helidon.integrations.eclipsestore.metrics; + +} diff --git a/integrations/eclipsestore/metrics/src/test/java/io/helidon/integrations/eclipsestore/metrics/EclipseStoreHealthMetricsTest.java b/integrations/eclipsestore/metrics/src/test/java/io/helidon/integrations/eclipsestore/metrics/EclipseStoreHealthMetricsTest.java new file mode 100644 index 00000000000..60cc43b1fe7 --- /dev/null +++ b/integrations/eclipsestore/metrics/src/test/java/io/helidon/integrations/eclipsestore/metrics/EclipseStoreHealthMetricsTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.eclipsestore.metrics; + +import io.helidon.metrics.api.Gauge; +import io.helidon.metrics.api.Metrics; +import org.eclipse.serializer.util.X; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.StorageRawFileStatistics; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Date; +import java.util.Set; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +// TODO disabled +@Disabled +class EclipseStoreHealthMetricsTest { + + @BeforeAll + static void init() { + EmbeddedStorageManager embeddedStorageManager = Mockito.mock(EmbeddedStorageManager.class); + + Mockito.when(embeddedStorageManager.createStorageStatistics()).thenReturn( + StorageRawFileStatistics.New( + new Date(System.currentTimeMillis()), + 42, + 1001, + 2002, + X.emptyTable())); + + EclipseStoreHealthMetricsSupport.builder(embeddedStorageManager).build().registerMetrics(); + } + + @Test + void testGlobalFileCount() { + var firstGauge = findFirstGauge("eclipsestore.globalFileCount"); + + long value = (long) firstGauge.value(); + assertThat("firstGauge eclipsestore.globalFileCount", value, is(42L)); + } + + @Test + void testLivDataLength() { + var metricGauge = findFirstGauge("eclipsestore.liveDataLength"); + + long value = (long) metricGauge.value(); + assertThat("metric eclipsestore.liveDataLength", value, is(1001L)); + } + + @Test + void testTotalDataLength() { + var firstGauge = findFirstGauge("eclipsestore.totalDataLength"); + + long value = (long) firstGauge.value(); + assertThat("firstGauge eclipsestore.totalDataLength", value, is(2002L)); + } + + private Gauge findFirstGauge(String name) { + return Metrics.globalRegistry().gauge(name, Set.of()).orElse(null); + } + +} diff --git a/integrations/eclipsestore/pom.xml b/integrations/eclipsestore/pom.xml new file mode 100644 index 00000000000..74f66dfc78c --- /dev/null +++ b/integrations/eclipsestore/pom.xml @@ -0,0 +1,42 @@ + + + + + 4.0.0 + + io.helidon.integrations + helidon-integrations-project + 4.0.0-SNAPSHOT + + + io.helidon.integrations.eclipsestore + helidon-integrations-eclipsestore-project + Helidon Integrations Eclipse Store Project + + pom + + + cache + cdi + core + health + metrics + + diff --git a/integrations/microstream/README.md b/integrations/microstream/README.md index c638db7d98d..c320ac08243 100644 --- a/integrations/microstream/README.md +++ b/integrations/microstream/README.md @@ -1,5 +1,8 @@ # Microstream integration into Helidon +**[MICROSTREAM](https://microstream.one/) HAS BECOME [ECLIPSE STORE](https://eclipsestore.io/). HELIDON USERS ARE ENCOURAGED TO TRANSITION TO +THE EQUIVALENT HELIDON ECLIPSE STORE EXTENSION!** + This projects add [Microstream](https://microstream.one) support to Helidon. The official [Microstream documentation](https://manual.docs.microstream.one/) can be found here. diff --git a/integrations/microstream/cache/src/main/java/module-info.java b/integrations/microstream/cache/src/main/java/module-info.java index 650f5931a44..a7c1ca1eba7 100644 --- a/integrations/microstream/cache/src/main/java/module-info.java +++ b/integrations/microstream/cache/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 Oracle and/or its affiliates. + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,4 +26,4 @@ exports io.helidon.integrations.microstream.cache; -} \ No newline at end of file +} diff --git a/integrations/microstream/cdi/src/main/java/module-info.java b/integrations/microstream/cdi/src/main/java/module-info.java index 8ab46568393..2804f5d9223 100644 --- a/integrations/microstream/cdi/src/main/java/module-info.java +++ b/integrations/microstream/cdi/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 Oracle and/or its affiliates. + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,14 +17,14 @@ import io.helidon.common.features.api.Aot; import io.helidon.common.features.api.Feature; import io.helidon.common.features.api.HelidonFlavor; -import io.helidon.common.features.api.Preview; /** * Provides CDI support for Microstream integration. * * @provides jakarta.enterprise.inject.spi.Extension */ -@Preview + + @Feature(value = "Microstream", description = "Microstream Integration", in = HelidonFlavor.MP, @@ -37,11 +37,8 @@ requires io.helidon.integrations.microstream.cache; requires io.helidon.integrations.microstream; requires jakarta.annotation; - //requires microstream.base; requires microstream.cache; - //requires microstream.persistence; requires microstream.storage.embedded; - //requires microstream.storage; requires static io.helidon.common.features.api; diff --git a/integrations/microstream/core/src/main/java/module-info.java b/integrations/microstream/core/src/main/java/module-info.java index 4103e8e5eca..9ce8467ffd6 100644 --- a/integrations/microstream/core/src/main/java/module-info.java +++ b/integrations/microstream/core/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 Oracle and/or its affiliates. + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,11 @@ import io.helidon.common.features.api.Aot; import io.helidon.common.features.api.Feature; import io.helidon.common.features.api.HelidonFlavor; -import io.helidon.common.features.api.Preview; + /** * Provides support for Microstream core features integration. */ -@Preview @Feature(value = "Microstream", description = "Microstream Integration", in = HelidonFlavor.SE, diff --git a/integrations/microstream/health/src/main/java/module-info.java b/integrations/microstream/health/src/main/java/module-info.java index ce9a6bc2800..cad5270803d 100644 --- a/integrations/microstream/health/src/main/java/module-info.java +++ b/integrations/microstream/health/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 Oracle and/or its affiliates. + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,4 +25,4 @@ exports io.helidon.integrations.microstream.health; -} \ No newline at end of file +} diff --git a/integrations/microstream/metrics/src/main/java/module-info.java b/integrations/microstream/metrics/src/main/java/module-info.java index dd467958967..ead2132e599 100644 --- a/integrations/microstream/metrics/src/main/java/module-info.java +++ b/integrations/microstream/metrics/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 Oracle and/or its affiliates. + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,4 +28,4 @@ exports io.helidon.integrations.microstream.metrics; -} \ No newline at end of file +} diff --git a/integrations/pom.xml b/integrations/pom.xml index b4334b274df..02596a6bafa 100644 --- a/integrations/pom.xml +++ b/integrations/pom.xml @@ -53,6 +53,7 @@ openapi-ui vault microstream + eclipsestore