diff --git a/.gitignore b/.gitignore index 09a65951451..e846e4963ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*~ *.class *.ctxt .mtj.tmp/ diff --git a/etc/scripts/build.sh b/etc/scripts/build.sh index f02009867a8..4a664aaf5fb 100755 --- a/etc/scripts/build.sh +++ b/etc/scripts/build.sh @@ -43,9 +43,10 @@ if [ "${WERCKER}" = "true" ] ; then apt-get update && apt-get -y install graphviz fi +inject_credentials + mvn -f ${WS_DIR}/pom.xml \ clean install \ - -Pexamples,spotbugs,javadoc,docs,sources,ossrh-releases \ - --fail-at-end + -Pexamples,spotbugs,javadoc,docs,sources,ossrh-releases -examples/archetypes/test-archetypes.sh \ No newline at end of file +examples/archetypes/test-archetypes.sh diff --git a/etc/scripts/release.sh b/etc/scripts/release.sh index 837bc79161e..fb07f2191bc 100755 --- a/etc/scripts/release.sh +++ b/etc/scripts/release.sh @@ -181,55 +181,6 @@ update_version(){ fi } -inject_credentials(){ - # Add private_key from IDENTITY_FILE - if [ -n "${IDENTITY_FILE}" ] && [ ! -e ~/.ssh ]; then - mkdir ~/.ssh/ 2>/dev/null || true - echo -e "${IDENTITY_FILE}" > ~/.ssh/id_rsa - chmod og-rwx ~/.ssh/id_rsa - echo -e "Host *" >> ~/.ssh/config - echo -e "\tStrictHostKeyChecking no" >> ~/.ssh/config - echo -e "\tUserKnownHostsFile /dev/null" >> ~/.ssh/config - fi - - # Add GPG key pair - if [ -n "${GPG_PUBLIC_KEY}" ] && [ -n "${GPG_PRIVATE_KEY}" ] ; then - mkdir ~/.gnupg 2>/dev/null || true - chmod 700 ~/.gnupg - echo "pinentry-mode loopback" > ~/.gnupg/gpg.conf - echo -e "${GPG_PUBLIC_KEY}" > ~/.gnupg/helidon_pub.gpg - gpg --import --no-tty --batch ~/.gnupg/helidon_pub.gpg - echo -e "${GPG_PRIVATE_KEY}" > ~/.gnupg/helidon_sec.gpg - gpg --allow-secret-key-import --import --no-tty --batch ~/.gnupg/helidon_sec.gpg - fi - - # Add docker config from DOCKER_CONFIG_FILE - if [ -n "${DOCKER_CONFIG_FILE}" ] && [ ! -e ~/.docker ]; then - mkdir ~/.docker/ 2>/dev/null || true - printf "${DOCKER_CONFIG_FILE}" > ~/.docker/config.json - chmod og-rwx ~/.docker/config.json - fi - - # Add maven settings from MAVEN_SETTINGS_FILE - if [ -n "${MAVEN_SETTINGS_FILE}" ] ; then - mkdir ~/.m2/ 2>/dev/null || true - echo -e "${MAVEN_SETTINGS_FILE}" > ~/.m2/settings.xml - fi - - # Add maven settings security from MAVEN_SETTINGS_SECURITY_FILE - if [ -n "${MAVEN_SETTINGS_SECURITY_FILE}" ] ; then - mkdir ~/.m2/ 2>/dev/null || true - echo -e "${MAVEN_SETTINGS_SECURITY_FILE}" > ~/.m2/settings-security.xml - fi - - # Add maven settings security from MAVEN_SETTINGS_SECURITY_FILE - # Only if none exist on the system - if [ -n "${MAVEN_SETTINGS_SECURITY_FILE}" ] && [ ! -e ~/.m2/settings-security.xml ]; then - mkdir ~/.m2/ 2>/dev/null || true - echo "${MAVEN_SETTINGS_SECURITY_FILE}" > ~/.m2/settings-security.xml - fi -} - release_build(){ # Inject credentials in CI env inject_credentials diff --git a/etc/scripts/wercker-env.sh b/etc/scripts/wercker-env.sh index 9b4e8d40401..2820146188e 100644 --- a/etc/scripts/wercker-env.sh +++ b/etc/scripts/wercker-env.sh @@ -17,7 +17,70 @@ # WERCKER=true when running inside a wercker pipeline +inject_credentials(){ + # Add private_key from IDENTITY_FILE + if [ -n "${IDENTITY_FILE}" ] && [ ! -e ~/.ssh ]; then + mkdir ~/.ssh/ 2>/dev/null || true + echo -e "${IDENTITY_FILE}" > ~/.ssh/id_rsa + chmod og-rwx ~/.ssh/id_rsa + echo -e "Host *" >> ~/.ssh/config + echo -e "\tStrictHostKeyChecking no" >> ~/.ssh/config + echo -e "\tUserKnownHostsFile /dev/null" >> ~/.ssh/config + fi + + # Add GPG key pair + if [ -n "${GPG_PUBLIC_KEY}" ] && [ -n "${GPG_PRIVATE_KEY}" ] ; then + mkdir ~/.gnupg 2>/dev/null || true + chmod 700 ~/.gnupg + echo "pinentry-mode loopback" > ~/.gnupg/gpg.conf + echo -e "${GPG_PUBLIC_KEY}" > ~/.gnupg/helidon_pub.gpg + gpg --import --no-tty --batch ~/.gnupg/helidon_pub.gpg + echo -e "${GPG_PRIVATE_KEY}" > ~/.gnupg/helidon_sec.gpg + gpg --allow-secret-key-import --import --no-tty --batch ~/.gnupg/helidon_sec.gpg + fi + + # Add docker config from DOCKER_CONFIG_FILE + if [ -n "${DOCKER_CONFIG_FILE}" ] && [ ! -e ~/.docker ]; then + mkdir ~/.docker/ 2>/dev/null || true + printf "${DOCKER_CONFIG_FILE}" > ~/.docker/config.json + chmod og-rwx ~/.docker/config.json + fi + + # Add maven settings from MAVEN_SETTINGS_FILE + if [ -n "${MAVEN_SETTINGS_FILE}" ] ; then + mkdir ~/.m2/ 2>/dev/null || true + echo -e "${MAVEN_SETTINGS_FILE}" > ~/.m2/settings.xml + fi + + # Add maven settings security from MAVEN_SETTINGS_SECURITY_FILE + # Only if none exist on the system + if [ -n "${MAVEN_SETTINGS_SECURITY_FILE}" ] && [ ! -e ~/.m2/settings-security.xml ]; then + mkdir ~/.m2/ 2>/dev/null || true + echo "${MAVEN_SETTINGS_SECURITY_FILE}" > ~/.m2/settings-security.xml + fi +} + if [ "${WERCKER}" = "true" ] ; then - export MAVEN_OPTS="-Dmaven.repo.local=${WERCKER_CACHE_DIR}/local_repository" + export MAVEN_OPTS="-Dmaven.repo.local=${WERCKER_CACHE_DIR}/local_repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" rm -rf ~/.m2/settings* ~/.gitconfig ~/.ssh ${WERCKER_CACHE_DIR}/local_repository/io/helidon + # Work around https://github.com/oracle/oci-java-sdk/issues/25 + TEMP_OCI_SDK_DIR=$(mktemp -d "oci-java-sdk.XXX") + git clone \ + --depth 1 \ + --branch \ + v$(mvn -B -f "${WERCKER_ROOT}/pom.xml" \ + -Dexpression=version.lib.oci-java-sdk-objectstorage org.apache.maven.plugins:maven-help-plugin:3.1.0:evaluate | grep '^[0-9]') \ + "https://github.com/oracle/oci-java-sdk.git" \ + "${TEMP_OCI_SDK_DIR}" && \ + mvn -B -U -f "${TEMP_OCI_SDK_DIR}/pom.xml" \ + -Dmaven.test.skip=true \ + -Dmaven.source.skip=true \ + -Dmaven.javadoc.skip=true \ + -Dlombok.delombok.skip=true \ + -pl bmc-objectstorage \ + -am \ + install && \ + rm -rf "${TEMP_OCI_SDK_DIR}" fi + + diff --git a/examples/integrations/cdi/datasource-hikaricp/pom.xml b/examples/integrations/cdi/datasource-hikaricp/pom.xml new file mode 100644 index 00000000000..70b19881bb7 --- /dev/null +++ b/examples/integrations/cdi/datasource-hikaricp/pom.xml @@ -0,0 +1,239 @@ + + + + 4.0.0 + + helidon-integrations-examples-datasource-hikaricp + + Helidon Integrations: DataSource/HikariCP Example + Helidon Integrations DataSource Hikari Connection Pool Example + + + io.helidon.integrations.examples + helidon-integrations-examples-project + 0.10.3-SNAPSHOT + + + + + + io.helidon.integrations.cdi + helidon-integrations-cdi-datasource-hikaricp + ${project.version} + + + + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.hamcrest + hamcrest-all + test + + + + + + com.oracle.jdbc + ojdbc8 + runtime + + + + org.jboss.weld.se + weld-se-core + runtime + + + org.jboss.spec.javax.el + jboss-el-api_3.0_spec + + + org.jboss.spec.javax.interceptor + jboss-interceptors-api_1.2_spec + + + + + + io.helidon.integrations.cdi + helidon-integrations-cdi-datasource-hikaricp + runtime + + + + org.jboss + jandex + runtime + + + + io.helidon.microprofile.server + helidon-microprofile-server + runtime + + + org.glassfish.hk2.external + javax.inject + + + + + + io.helidon.microprofile.config + helidon-microprofile-config-cdi + runtime + + + + javax.activation + javax.activation-api + runtime + + + + + + javax.enterprise + cdi-api + compile + + + + javax.ws.rs + javax.ws.rs-api + compile + + + + redis.clients + jedis + compile + + + + org.eclipse.microprofile.config + microprofile-config-api + compile + + + + + + + + src/main/resources + true + + + + + maven-dependency-plugin + + + Copy all project dependencies to ${project.build.directory}/${dependenciesDirectory} for Docker image construction + prepare-package + + copy-dependencies + + + ${project.build.directory}/${dependenciesDirectory} + false + false + true + true + runtime + test + + + + + + maven-jar-plugin + + + + true + ${dependenciesDirectory} + io.helidon.microprofile.server.Main + + + + + + maven-resources-plugin + + + Copy Dockerfile and Kubernetes resources + process-resources + + copy-resources + + + ${project.build.directory} + + + src/main/docker + true + + Dockerfile + + + + true + src/main/k8s + + datasource.secrets.template + app.yaml + + + + + + + + + maven-surefire-plugin + + + + + + + + + + libs + ${project.artifactId} + ${project.basedir}/src/main/spotbugs/exclusions.xml + + + diff --git a/examples/integrations/cdi/datasource-hikaricp/src/main/docker/Dockerfile b/examples/integrations/cdi/datasource-hikaricp/src/main/docker/Dockerfile new file mode 100644 index 00000000000..168bc122e14 --- /dev/null +++ b/examples/integrations/cdi/datasource-hikaricp/src/main/docker/Dockerfile @@ -0,0 +1,27 @@ +# +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +FROM openjdk:8-jre-alpine +EXPOSE 8080 +RUN mkdir /app +COPY ${project.build.finalName}.jar /app +COPY ${dependenciesDirectory} /app/${dependenciesDirectory} +CMD [ \ +"sh", "-c", \ +"exec java \ +-XX:+UnlockExperimentalVMOptions \ +-XX:+UseCGroupMemoryLimitForHeap \ +-jar /app/${project.build.finalName}.jar" \ +] diff --git a/examples/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/examples/datasource/hikaricp/jaxrs/TablesResource.java b/examples/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/examples/datasource/hikaricp/jaxrs/TablesResource.java new file mode 100644 index 00000000000..25b536ca8c7 --- /dev/null +++ b/examples/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/examples/datasource/hikaricp/jaxrs/TablesResource.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.integrations.examples.datasource.hikaricp.jaxrs; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Objects; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.inject.Named; +import javax.sql.DataSource; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * A JAX-RS resource class in {@linkplain ApplicationScoped + * application scope} rooted at {@code /tables}. + * + * @see #get() + */ +@Path("/tables") +@ApplicationScoped +public class TablesResource { + + private final DataSource dataSource; + + /** + * Creates a new {@link TablesResource}. + * + * @param dataSource the {@link DataSource} to use to acquire + * database table names; must not be {@code null} + * + * @exception NullPointerException if {@code dataSource} is {@code + * null} + */ + @Inject + public TablesResource(@Named("example") final DataSource dataSource) { + super(); + this.dataSource = Objects.requireNonNull(dataSource); + } + + /** + * Returns a {@link Response} which, if successful, contains a + * newline-separated list of Oracle database table names. + * + *

This method never returns {@code null}.

+ * + * @return a non-{@code null} {@link Response} + * + * @exception SQLException if a database error occurs + */ + @GET + @Produces(MediaType.TEXT_PLAIN) + public Response get() throws SQLException { + final StringBuilder sb = new StringBuilder(); + try (Connection connection = this.dataSource.getConnection(); + PreparedStatement ps = + connection.prepareStatement(" SELECT TABLE_NAME" + + " FROM ALL_TABLES " + + "ORDER BY TABLE_NAME ASC"); + ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + sb.append(rs.getString(1)).append("\n"); + } + } + final Response returnValue = Response.ok() + .entity(sb.toString()) + .build(); + return returnValue; + } + +} diff --git a/examples/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/examples/datasource/hikaricp/jaxrs/package-info.java b/examples/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/examples/datasource/hikaricp/jaxrs/package-info.java new file mode 100644 index 00000000000..372c3918304 --- /dev/null +++ b/examples/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/examples/datasource/hikaricp/jaxrs/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides JAX-RS-related classes and interfaces for this example + * project. + */ +package io.helidon.integrations.examples.datasource.hikaricp.jaxrs; diff --git a/examples/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/beans.xml b/examples/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..607db5743cc --- /dev/null +++ b/examples/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/beans.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/examples/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/microprofile-config.properties b/examples/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/microprofile-config.properties new file mode 100644 index 00000000000..8a8e63c52ea --- /dev/null +++ b/examples/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/microprofile-config.properties @@ -0,0 +1,29 @@ +# +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Default database properties. +# See +# https://container-registry.oracle.com/pls/apex/f?p=113:4:102624334361221::NO::: +# and +# https://technology.amis.nl/2017/11/18/run-oracle-database-in-docker-using-prebaked-image-from-oracle-container-registry-a-two-minute-guide/ +javax.sql.DataSource.example.dataSourceClassName=oracle.jdbc.pool.OracleDataSource +javax.sql.DataSource.example.dataSource.url = jdbc:oracle:thin:@localhost:1527:ORCL +javax.sql.DataSource.example.dataSource.user = sys as sysoper +javax.sql.DataSource.example.dataSource.password = Oracle + +# Microprofile server properties +server.port=8080 +server.host=0.0.0.0 diff --git a/examples/integrations/cdi/datasource-hikaricp/src/main/spotbugs/exclusions.xml b/examples/integrations/cdi/datasource-hikaricp/src/main/spotbugs/exclusions.xml new file mode 100644 index 00000000000..0e98aa554c9 --- /dev/null +++ b/examples/integrations/cdi/datasource-hikaricp/src/main/spotbugs/exclusions.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/examples/integrations/cdi/jedis/pom.xml b/examples/integrations/cdi/jedis/pom.xml new file mode 100644 index 00000000000..4f23e201883 --- /dev/null +++ b/examples/integrations/cdi/jedis/pom.xml @@ -0,0 +1,232 @@ + + + + 4.0.0 + + helidon-integrations-examples-jedis + + Helidon Integrations Jedis Example + ${project.name} + + + io.helidon.integrations.examples + helidon-integrations-examples-project + 0.10.3-SNAPSHOT + + + + + + io.helidon.integrations.cdi + helidon-integrations-cdi-jedis + ${project.version} + + + + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.hamcrest + hamcrest-all + test + + + + + + org.jboss.weld.se + weld-se-core + runtime + + + org.jboss.spec.javax.el + jboss-el-api_3.0_spec + + + org.jboss.spec.javax.interceptor + jboss-interceptors-api_1.2_spec + + + + + + io.helidon.integrations.cdi + helidon-integrations-cdi-jedis + runtime + + + + org.jboss + jandex + runtime + + + + io.helidon.microprofile.server + helidon-microprofile-server + runtime + + + org.glassfish.hk2.external + javax.inject + + + + + + io.helidon.microprofile.config + helidon-microprofile-config-cdi + runtime + + + + javax.activation + javax.activation-api + runtime + + + + + + javax.enterprise + cdi-api + compile + + + + javax.ws.rs + javax.ws.rs-api + compile + + + + redis.clients + jedis + compile + + + + org.eclipse.microprofile.config + microprofile-config-api + compile + + + + + + + + src/main/resources + true + + + + + maven-dependency-plugin + + + Copy all project dependencies to ${project.build.directory}/${dependenciesDirectory} for Docker image construction + prepare-package + + copy-dependencies + + + ${project.build.directory}/${dependenciesDirectory} + false + false + true + true + runtime + test + + + + + + maven-jar-plugin + + + + true + ${dependenciesDirectory} + io.helidon.microprofile.server.Main + + + + + + maven-resources-plugin + + + Copy Dockerfile and Kubernetes resources + process-resources + + copy-resources + + + ${project.build.directory} + + + src/main/docker + true + + Dockerfile + + + + true + src/main/k8s + + jedis.secrets.template + app.yaml + + + + + + + + + maven-surefire-plugin + + + + + + + + + + libs + ${project.artifactId} + ${project.basedir}/src/main/spotbugs/exclusions.xml + + + diff --git a/examples/integrations/cdi/jedis/src/main/docker/Dockerfile b/examples/integrations/cdi/jedis/src/main/docker/Dockerfile new file mode 100644 index 00000000000..168bc122e14 --- /dev/null +++ b/examples/integrations/cdi/jedis/src/main/docker/Dockerfile @@ -0,0 +1,27 @@ +# +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +FROM openjdk:8-jre-alpine +EXPOSE 8080 +RUN mkdir /app +COPY ${project.build.finalName}.jar /app +COPY ${dependenciesDirectory} /app/${dependenciesDirectory} +CMD [ \ +"sh", "-c", \ +"exec java \ +-XX:+UnlockExperimentalVMOptions \ +-XX:+UseCGroupMemoryLimitForHeap \ +-jar /app/${project.build.finalName}.jar" \ +] diff --git a/examples/integrations/cdi/jedis/src/main/java/io/helidon/integrations/examples/jedis/jaxrs/Application.java b/examples/integrations/cdi/jedis/src/main/java/io/helidon/integrations/examples/jedis/jaxrs/Application.java new file mode 100644 index 00000000000..93643524e0f --- /dev/null +++ b/examples/integrations/cdi/jedis/src/main/java/io/helidon/integrations/examples/jedis/jaxrs/Application.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.integrations.examples.jedis.jaxrs; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.ApplicationPath; + +/** + * A JAX-RS {@link javax.ws.rs.core.Application application} in + * {@linkplain ApplicationScoped application scope}. + * + * @see #getClasses() + */ +@ApplicationScoped +@ApplicationPath("/") +public class Application extends javax.ws.rs.core.Application { + + private final Set> classes; + + /** + * Creates a new {@link Application}. + */ + public Application() { + super(); + final Set> classes = new HashSet<>(); + classes.add(RedisClientResource.class); + this.classes = Collections.unmodifiableSet(classes); + } + + /** + * Returns a non-{@code null} {@linkplain + * java.util.Collections#unmodifiableSet(Set) immutable + * Set} of {@link Class}es that comprise this JAX-RS + * application. + * + * @return a non-{@code null} {@linkplain + * java.util.Collections#unmodifiableSet(Set) immutable + * Set} + */ + @Override + public Set> getClasses() { + return this.classes; + } + +} diff --git a/examples/integrations/cdi/jedis/src/main/java/io/helidon/integrations/examples/jedis/jaxrs/RedisClientResource.java b/examples/integrations/cdi/jedis/src/main/java/io/helidon/integrations/examples/jedis/jaxrs/RedisClientResource.java new file mode 100644 index 00000000000..138e9329b87 --- /dev/null +++ b/examples/integrations/cdi/jedis/src/main/java/io/helidon/integrations/examples/jedis/jaxrs/RedisClientResource.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.integrations.examples.jedis.jaxrs; + +import java.util.Objects; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import redis.clients.jedis.Jedis; + +/** + * A JAX-RS resource class rooted at {@code /jedis}. + * + * @see #get(String) + * + * @see #set(UriInfo, String, String) + * + * @see #del(String) + */ +@Path("/jedis") +@ApplicationScoped +public class RedisClientResource { + + private final Provider clientProvider; + + /** + * Creates a new {@link RedisClientResource}. + * + * @param clientProvider a {@link Provider} of a {@link Jedis} + * instance; must not be {@code null} + * + * @exception NullPointerException if {@code clientProvider} is + * {@code null} + */ + @Inject + public RedisClientResource(final Provider clientProvider) { + super(); + this.clientProvider = Objects.requireNonNull(clientProvider); + } + + /** + * Returns a non-{@code null} {@link Response} which, if successful, + * will contain any value indexed under the supplied Redis key. + * + *

This method never returns {@code null}.

+ * + * @param key the key whose value should be deleted; must not be + * {@code null} + * + * @return a non-{@code null} {@link Response} + * + * @see #set(UriInfo, String, String) + * + * @see #del(String) + */ + @GET + @Path("/{key}") + @Produces(MediaType.TEXT_PLAIN) + public Response get(@PathParam("key") final String key) { + final Response returnValue; + if (key == null || key.isEmpty()) { + returnValue = Response.status(400) + .build(); + } else { + final String response = this.clientProvider.get().get(key); + if (response == null) { + returnValue = Response.status(404) + .build(); + } else { + returnValue = Response.ok() + .entity(response) + .build(); + } + } + return returnValue; + } + + /** + * Sets a value under a key in a Redis system. + * + * @param uriInfo a {@link UriInfo} describing the current request; + * must not be {@code null} + * + * @param key the key in question; must not be {@code null} + * + * @param value the value to set; may be {@code null} + * + * @return a non-{@code null} {@link Response} indicating the status + * of the operation + * + * @exception NullPointerException if {@code uriInfo} is {@code + * null} + * + * @see #del(String) + */ + @PUT + @Path("/{key}") + @Consumes(MediaType.TEXT_PLAIN) + public Response set(@Context final UriInfo uriInfo, + @PathParam("key") final String key, + final String value) { + Objects.requireNonNull(uriInfo); + final Response returnValue; + if (key == null || key.isEmpty() || value == null) { + returnValue = Response.status(400) + .build(); + } else { + final Object priorValue = this.clientProvider.get().getSet(key, value); + if (priorValue == null) { + returnValue = Response.created(uriInfo.getRequestUri()) + .build(); + } else { + returnValue = Response.ok() + .build(); + } + } + return returnValue; + } + + /** + * Deletes a value from Redis. + * + * @param key the key identifying the value to delete; must not be + * {@code null} + * + * @return a non-{@code null} {@link Response} describing the result + * of the operation + * + * @see #get(String) + * + * @see #set(UriInfo, String, String) + */ + @DELETE + @Path("/{key}") + @Produces(MediaType.TEXT_PLAIN) + public Response del(@PathParam("key") final String key) { + final Response returnValue; + if (key == null || key.isEmpty()) { + returnValue = Response.status(400) + .build(); + } else { + final Long numberOfKeysDeleted = this.clientProvider.get().del(key); + if (numberOfKeysDeleted == null || numberOfKeysDeleted.longValue() <= 0L) { + returnValue = Response.status(404) + .build(); + } else { + returnValue = Response.noContent() + .build(); + } + } + return returnValue; + } + +} diff --git a/examples/integrations/cdi/jedis/src/main/java/io/helidon/integrations/examples/jedis/jaxrs/package-info.java b/examples/integrations/cdi/jedis/src/main/java/io/helidon/integrations/examples/jedis/jaxrs/package-info.java new file mode 100644 index 00000000000..d9d222a8d99 --- /dev/null +++ b/examples/integrations/cdi/jedis/src/main/java/io/helidon/integrations/examples/jedis/jaxrs/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides JAX-RS-related classes and interfaces for this example + * project. + */ +package io.helidon.integrations.examples.jedis.jaxrs; diff --git a/examples/integrations/cdi/jedis/src/main/k8s/app.yaml b/examples/integrations/cdi/jedis/src/main/k8s/app.yaml new file mode 100644 index 00000000000..29a1257ac02 --- /dev/null +++ b/examples/integrations/cdi/jedis/src/main/k8s/app.yaml @@ -0,0 +1,50 @@ +# +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +kind: Service +apiVersion: v1 +metadata: + name: ${project.artifactId} + labels: + app: ${project.artifactId} +spec: + type: NodePort + selector: + app: ${project.artifactId} + ports: + - port: 8080 + targetPort: 8080 + name: http +--- +kind: Deployment +apiVersion: extensions/v1beta1 +metadata: + name: ${project.artifactId} +spec: + replicas: 1 + template: + metadata: + labels: + app: ${project.artifactId} + version: v1 + spec: + containers: + - name: ${project.artifactId} + image: ${project.artifactId} + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 +--- diff --git a/examples/integrations/cdi/jedis/src/main/resources/META-INF/beans.xml b/examples/integrations/cdi/jedis/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..607db5743cc --- /dev/null +++ b/examples/integrations/cdi/jedis/src/main/resources/META-INF/beans.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/examples/integrations/cdi/jedis/src/main/resources/META-INF/microprofile-config.properties b/examples/integrations/cdi/jedis/src/main/resources/META-INF/microprofile-config.properties new file mode 100644 index 00000000000..71e89c2ff14 --- /dev/null +++ b/examples/integrations/cdi/jedis/src/main/resources/META-INF/microprofile-config.properties @@ -0,0 +1,22 @@ +# +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Jedis properties +redis.clients.jedis.JedisPool.default.port=6379 + +# Microprofile server properties +server.port=8080 +server.host=0.0.0.0 diff --git a/examples/integrations/cdi/jedis/src/main/spotbugs/exclusions.xml b/examples/integrations/cdi/jedis/src/main/spotbugs/exclusions.xml new file mode 100644 index 00000000000..f5b4e738068 --- /dev/null +++ b/examples/integrations/cdi/jedis/src/main/spotbugs/exclusions.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/examples/integrations/cdi/oci-objectstorage/README.adoc b/examples/integrations/cdi/oci-objectstorage/README.adoc new file mode 100644 index 00000000000..8f6278666bd --- /dev/null +++ b/examples/integrations/cdi/oci-objectstorage/README.adoc @@ -0,0 +1,24 @@ += OCI Object Storage CDI Integration Example + +== Build + +---- +mvn clean install +---- + +== Deploy to Kubernetes + +First you need to create Kubernetes secrets to store sensitive OCI +information. + +To start, make a copy of the Bash script located (after building the +project) in `target/oci.secrets.template` and name it whatever you +like. This guide will refer to it as, simply, `oci.secrets`. + +Now edit `oci.secrets` to contain the information you'll need to +connect to OCI services. + +Once you have properly customized the variables in `oci.secrets`, you +can execute it. `kubectl`, invoked by the script, will create a +Kubernetes secret that will contain all the OCI connectivity +information. diff --git a/examples/integrations/cdi/oci-objectstorage/pom.xml b/examples/integrations/cdi/oci-objectstorage/pom.xml new file mode 100644 index 00000000000..9b4c0b0e691 --- /dev/null +++ b/examples/integrations/cdi/oci-objectstorage/pom.xml @@ -0,0 +1,249 @@ + + + + 4.0.0 + + helidon-integrations-examples-oci-objectstorage + + Helidon Integrations OCI ObjectStorage Example + ${project.name} + + + io.helidon.integrations.examples + helidon-integrations-examples-project + 0.10.3-SNAPSHOT + + + + + + io.helidon.integrations.cdi + helidon-integrations-cdi-oci-objectstorage + ${project.version} + + + + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.hamcrest + hamcrest-all + test + + + + org.slf4j + slf4j-simple + test + + + + + + org.jboss.weld.se + weld-se-core + runtime + + + org.jboss.spec.javax.el + jboss-el-api_3.0_spec + + + org.jboss.spec.javax.interceptor + jboss-interceptors-api_1.2_spec + + + + + + io.helidon.integrations.cdi + helidon-integrations-cdi-oci-objectstorage + runtime + + + org.glassfish.hk2.external + javax.inject + + + + + + org.jboss + jandex + runtime + + + + io.helidon.microprofile.server + helidon-microprofile-server + runtime + + + + io.helidon.microprofile.config + helidon-microprofile-config-cdi + runtime + + + + javax.activation + javax.activation-api + runtime + + + + + + javax.enterprise + cdi-api + compile + + + + com.oracle.oci.sdk + oci-java-sdk-objectstorage + compile + pom + + + org.glassfish.hk2.external + javax.inject + + + + + + org.eclipse.microprofile.config + microprofile-config-api + compile + + + + + + + + src/main/resources + true + + + + + maven-dependency-plugin + + + Copy all project dependencies to ${project.build.directory}/${dependenciesDirectory} for Docker image construction + prepare-package + + copy-dependencies + + + ${project.build.directory}/${dependenciesDirectory} + false + false + true + true + runtime + test + + + + + + maven-jar-plugin + + + + true + ${dependenciesDirectory} + io.helidon.microprofile.server.Main + + + + + + maven-resources-plugin + + + Copy Dockerfile and Kubernetes resources + process-resources + + copy-resources + + + ${project.build.directory} + + + src/main/docker + true + + Dockerfile + + + + true + src/main/k8s + + oci.secrets.template + app.yaml + + + + + + + + + maven-surefire-plugin + + + ${oci.objectstorage.compartmentId} + ${oci.objectstorage.region} + + + + + + + + libs + ${project.artifactId} + ${project.basedir}/src/main/spotbugs/exclusions.xml + + TODO: For testing only, you need to set your OCI fingerprint here. For more information: https://docs.cloud.oracle.com/iaas/Content/API/Concepts/sdkconfig.htm + TODO: For testing only, you need to set the full path to your OCI keyFile here. For more information: https://docs.cloud.oracle.com/iaas/Content/API/Concepts/sdkconfig.htm + TODO: For testing only, you need to set your OCI keyFile passphrase here. For more information: https://docs.cloud.oracle.com/iaas/Content/API/Concepts/sdkconfig.htm + TODO: For testing only, you need to set your OCI tenancy here. For more information: https://docs.cloud.oracle.com/iaas/Content/API/Concepts/sdkconfig.htm + TODO: For testing only, you need to set your OCI user here. For more information: https://docs.cloud.oracle.com/iaas/Content/API/Concepts/sdkconfig.htm + TODO: For testing only, you need to set your OCI compartment ID here. For more information: https://docs.cloud.oracle.com/iaas/Content/API/Concepts/sdkconfig.htm + TODO: For testing only, you need to set your OCI region here. For more information: https://docs.cloud.oracle.com/iaas/Content/API/Concepts/sdkconfig.htm + + + diff --git a/examples/integrations/cdi/oci-objectstorage/src/main/docker/Dockerfile b/examples/integrations/cdi/oci-objectstorage/src/main/docker/Dockerfile new file mode 100644 index 00000000000..d103a8b591a --- /dev/null +++ b/examples/integrations/cdi/oci-objectstorage/src/main/docker/Dockerfile @@ -0,0 +1,34 @@ +# +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +FROM openjdk:8-jre-alpine +EXPOSE 8080 +RUN mkdir /app +COPY ${project.build.finalName}.jar /app +COPY ${dependenciesDirectory} /app/${dependenciesDirectory} +CMD [ \ +"sh", "-c", \ +"exec java \ +-Doci.auth.fingerprint=\"${OCI_AUTH_FINGERPRINT}\" \ +-Doci.auth.passphraseCharacters=\"${OCI_AUTH_PASSPHRASE}\" \ +-Doci.auth.privateKey=\"${OCI_AUTH_PRIVATEKEY}\" \ +-Doci.auth.tenancy=\"${OCI_AUTH_TENANCY}\" \ +-Doci.auth.user=\"${OCI_AUTH_USER}\" \ +-Doci.objectstorage.compartmentId=\"${OCI_OBJECTSTORAGE_COMPARTMENT}\" \ +-Doci.objectstorage.region=\"${OCI_OBJECTSTORAGE_REGION}\" \ +-XX:+UnlockExperimentalVMOptions \ +-XX:+UseCGroupMemoryLimitForHeap \ +-jar /app/${project.build.finalName}.jar" \ +] diff --git a/examples/integrations/cdi/oci-objectstorage/src/main/java/io/helidon/integrations/examples/oci/objectstorage/jaxrs/Application.java b/examples/integrations/cdi/oci-objectstorage/src/main/java/io/helidon/integrations/examples/oci/objectstorage/jaxrs/Application.java new file mode 100644 index 00000000000..4f2e10c1f49 --- /dev/null +++ b/examples/integrations/cdi/oci-objectstorage/src/main/java/io/helidon/integrations/examples/oci/objectstorage/jaxrs/Application.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.integrations.examples.oci.objectstorage.jaxrs; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.ApplicationPath; + +/** + * A JAX-RS {@linkplain javax.ws.rs.core.Application application} in + * {@linkplain ApplicationScoped application scope}. + * + * @see #getClasses() + */ +@ApplicationScoped +@ApplicationPath("/") +public class Application extends javax.ws.rs.core.Application { + + private final Set> classes; + + /** + * Creates a new {@link Application}. + */ + public Application() { + super(); + final Set> classes = new HashSet<>(); + classes.add(HelidonLogoResource.class); + this.classes = Collections.unmodifiableSet(classes); + } + + /** + * Returns a non-{@code null} {@linkplain + * java.util.Collections#unmodifiableSet(Set) immutable + * Set} of {@link Class}es that comprise this JAX-RS + * application. + * + * @return a non-{@code null} {@linkplain + * java.util.Collections#unmodifiableSet(Set) immutable + * Set} + */ + @Override + public Set> getClasses() { + return this.classes; + } + +} diff --git a/examples/integrations/cdi/oci-objectstorage/src/main/java/io/helidon/integrations/examples/oci/objectstorage/jaxrs/HelidonLogoResource.java b/examples/integrations/cdi/oci-objectstorage/src/main/java/io/helidon/integrations/examples/oci/objectstorage/jaxrs/HelidonLogoResource.java new file mode 100644 index 00000000000..a7972afe8ec --- /dev/null +++ b/examples/integrations/cdi/oci-objectstorage/src/main/java/io/helidon/integrations/examples/oci/objectstorage/jaxrs/HelidonLogoResource.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.integrations.examples.oci.objectstorage.jaxrs; + +import java.util.Objects; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import com.oracle.bmc.model.BmcException; +import com.oracle.bmc.objectstorage.ObjectStorage; +import com.oracle.bmc.objectstorage.requests.GetObjectRequest; +import com.oracle.bmc.objectstorage.responses.GetObjectResponse; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +/** + * A JAX-RS resource class rooted at {@code /logo}. + * + * @see #getLogo(String, String, String) + */ +@Path("/logo") +@ApplicationScoped +public class HelidonLogoResource { + + private final ObjectStorage client; + + private final String namespaceName; + + /** + * Creates a new {@link HelidonLogoResource}. + * + * @param client an {@link ObjectStorage} client; must not be {@code + * null} + * + * @param namespaceName the name of an OCI object storage namespace + * that will be used; must not be {@code null} + * + * @exception NullPointerException if either parameter is {@code + * null} + */ + @Inject + public HelidonLogoResource(final ObjectStorage client, + @ConfigProperty(name = "oci.objectstorage.namespace") final String namespaceName) { + super(); + this.client = Objects.requireNonNull(client); + this.namespaceName = Objects.requireNonNull(namespaceName); + } + + /** + * Returns a non-{@code null} {@link Response} which, if successful, + * will contain the object stored under the supplied {@code + * namespaceName}, {@code bucketName} and {@code objectName}. + * + * @param namespaceName the OCI object storage namespace to use; + * must not be {@code null} + * + * @param bucketName the OCI object storage bucket name to use; must + * not be {@code null} + * + * @param objectName the OCI object storage object name to use; must + * not be {@code null} + * + * @return a non-{@code null} {@link Response} describing the + * operation + * + * @exception NullPointerException if any of the parameters is + * {@code null} + */ + @GET + @Path("/{namespaceName}/{bucketName}/{objectName}") + @Produces(MediaType.WILDCARD) + public Response getLogo(@PathParam("namespaceName") String namespaceName, + @PathParam("bucketName") final String bucketName, + @PathParam("objectName") final String objectName) { + final Response returnValue; + if (bucketName == null || bucketName.isEmpty() || objectName == null || objectName.isEmpty()) { + returnValue = Response.status(400) + .build(); + } else { + if (namespaceName == null || namespaceName.isEmpty()) { + namespaceName = this.namespaceName; + } + Response temp = null; + try { + final GetObjectRequest request = GetObjectRequest.builder() + .namespaceName(namespaceName) + .bucketName(bucketName) + .objectName(objectName) + .build(); + assert request != null; + final GetObjectResponse response = this.client.getObject(request); + assert response != null; + final Long contentLength = response.getContentLength(); + assert contentLength != null; + if (contentLength.longValue() <= 0L) { + temp = Response.noContent() + .build(); + } else { + temp = Response.ok() + .type(response.getContentType()) + .entity(response.getInputStream()) + .build(); + } + } catch (final BmcException bmcException) { + final int statusCode = bmcException.getStatusCode(); + temp = Response.status(statusCode) + .build(); + } finally { + returnValue = temp; + } + } + return returnValue; + } + +} diff --git a/examples/integrations/cdi/oci-objectstorage/src/main/java/io/helidon/integrations/examples/oci/objectstorage/jaxrs/package-info.java b/examples/integrations/cdi/oci-objectstorage/src/main/java/io/helidon/integrations/examples/oci/objectstorage/jaxrs/package-info.java new file mode 100644 index 00000000000..14a15746748 --- /dev/null +++ b/examples/integrations/cdi/oci-objectstorage/src/main/java/io/helidon/integrations/examples/oci/objectstorage/jaxrs/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides JAX-RS-related classes and interfaces for this example + * project. + */ +package io.helidon.integrations.examples.oci.objectstorage.jaxrs; diff --git a/examples/integrations/cdi/oci-objectstorage/src/main/k8s/app.yaml b/examples/integrations/cdi/oci-objectstorage/src/main/k8s/app.yaml new file mode 100644 index 00000000000..45e60c91149 --- /dev/null +++ b/examples/integrations/cdi/oci-objectstorage/src/main/k8s/app.yaml @@ -0,0 +1,86 @@ +# +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +kind: Service +apiVersion: v1 +metadata: + name: ${project.artifactId} + labels: + app: ${project.artifactId} +spec: + type: NodePort + selector: + app: ${project.artifactId} + ports: + - port: 8080 + targetPort: 8080 + name: http +--- +kind: Deployment +apiVersion: extensions/v1beta1 +metadata: + name: ${project.artifactId} +spec: + replicas: 1 + template: + metadata: + labels: + app: ${project.artifactId} + version: v1 + spec: + containers: + - name: ${project.artifactId} + image: ${project.artifactId} + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + env: + - name: OCI_AUTH_FINGERPRINT + valueFrom: + secretKeyRef: + name: ${kubernetesSecretName} + key: OCI_AUTH_FINGERPRINT + - name: OCI_AUTH_PASSPHRASE + valueFrom: + secretKeyRef: + name: ${kubernetesSecretName} + key: OCI_AUTH_PASSPHRASE + - name: OCI_AUTH_PRIVATEKEY + valueFrom: + secretKeyRef: + name: ${kubernetesSecretName} + key: OCI_AUTH_PRIVATEKEY + - name: OCI_AUTH_TENANCY + valueFrom: + secretKeyRef: + name: ${kubernetesSecretName} + key: OCI_AUTH_TENANCY + - name: OCI_AUTH_USER + valueFrom: + secretKeyRef: + name: ${kubernetesSecretName} + key: OCI_AUTH_USER + - name: OCI_OBJECTSTORAGE_COMPARTMENT + valueFrom: + secretKeyRef: + name: ${kubernetesSecretName} + key: OCI_OBJECTSTORAGE_COMPARTMENT + - name: OCI_OBJECTSTORAGE_REGION + valueFrom: + secretKeyRef: + name: ${kubernetesSecretName} + key: OCI_OBJECTSTORAGE_REGION +--- diff --git a/examples/integrations/cdi/oci-objectstorage/src/main/k8s/oci.secrets.template b/examples/integrations/cdi/oci-objectstorage/src/main/k8s/oci.secrets.template new file mode 100644 index 00000000000..366bb687cba --- /dev/null +++ b/examples/integrations/cdi/oci-objectstorage/src/main/k8s/oci.secrets.template @@ -0,0 +1,82 @@ +#!/bin/sh +# +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file is a TEMPLATE for a shell script that will create +# Kubernetes secrets that will be used to deploy this example program +# to Kubernetes. If you are reading this from the +# src/main/resources/k8s directory, bear in mind that it is first +# copied with Maven filtering applied to the +# ${project.build.directory} directory, which is normally "target". +# +# You should take the target/oci.secrets.template file and make a copy +# of it. Then, customize the variables below in your copy to contain +# the required information. + +# The name of the secret that Kubernetes will create. It must consist of lower +# case alphanumeric characters, '-' or '.', and must start and end with an +# alphanumeric character. The regular expression used for validation is: +# [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* +SECRET_NAME=${kubernetesSecretName} + +# The fingerprint for the key pair being used. See +# https://docs.cloud.oracle.com/iaas/Content/API/Concepts/apisigningkey.htm#How3 +# for more information. +OCI_AUTH_FINGERPRINT= + +# The passphrase used for the key if it is encrypted. See +# https://docs.cloud.oracle.com/iaas/Content/API/Concepts/apisigningkey.htm#two +# for more information. +OCI_AUTH_PASSPHRASE= + +# The actual private key contents. This variable is set by default to +# contain the exact contents of the private key file output by the +# instructions found at +# https://docs.cloud.oracle.com/iaas/Content/API/Concepts/apisigningkey.htm#two. +# However you choose to set this variable, it must contain the exact +# private key contents, including newlines. +OCI_AUTH_PRIVATEKEY=$(cat ~/.oci/oci_api_key.pem) + +# The OCID of your tenancy. See +# https://docs.cloud.oracle.com/iaas/Content/API/Concepts/apisigningkey.htm#five +# for more information. +OCI_AUTH_TENANCY= + +# The OCID of your user. See +# https://docs.cloud.oracle.com/iaas/Content/API/Concepts/apisigningkey.htm#five +# for more information. For more on OCIDs in general, see +# https://docs.cloud.oracle.com/iaas/Content/General/Concepts/identifiers.htm#one. +OCI_AUTH_USER= + +# The OCID of the compartment containing the OCI object storage +# service you wish to access. For more on OCIDs in general, see +# https://docs.cloud.oracle.com/iaas/Content/General/Concepts/identifiers.htm#one. +OCI_OBJECTSTORAGE_COMPARTMENT= + +# The region to use. See +# https://docs.cloud.oracle.com/iaas/Content/General/Concepts/regions.htm +# for more information. +OCI_OBJECTSTORAGE_REGION= + +# The command below uses the variables above and should not need to be +# modified. +kubectl create secret generic ${SECRET_NAME} \ +--from-literal=OCI_AUTH_FINGERPRINT="${OCI_AUTH_FINGERPRINT}" \ +--from-literal=OCI_AUTH_PASSPHRASE="${OCI_AUTH_PASSPHRASE}" \ +--from-literal=OCI_AUTH_PRIVATEKEY="${OCI_AUTH_PRIVATEKEY}" \ +--from-literal=OCI_AUTH_TENANCY="${OCI_AUTH_TENANCY}" \ +--from-literal=OCI_AUTH_USER="${OCI_AUTH_USER}" \ +--from-literal=OCI_OBJECTSTORAGE_COMPARTMENT="${OCI_OBJECTSTORAGE_COMPARTMENT}" \ +--from-literal=OCI_OBJECTSTORAGE_REGION="${OCI_OBJECTSTORAGE_REGION}" diff --git a/examples/integrations/cdi/oci-objectstorage/src/main/resources/META-INF/beans.xml b/examples/integrations/cdi/oci-objectstorage/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..607db5743cc --- /dev/null +++ b/examples/integrations/cdi/oci-objectstorage/src/main/resources/META-INF/beans.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/examples/integrations/cdi/oci-objectstorage/src/main/resources/META-INF/microprofile-config.properties b/examples/integrations/cdi/oci-objectstorage/src/main/resources/META-INF/microprofile-config.properties new file mode 100644 index 00000000000..254b9341b77 --- /dev/null +++ b/examples/integrations/cdi/oci-objectstorage/src/main/resources/META-INF/microprofile-config.properties @@ -0,0 +1,22 @@ +# +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +oci.objectstorage.namespace=${oci.objectstorage.namespaceName} + + +# Microprofile server properties +server.port=8080 +server.host=0.0.0.0 diff --git a/examples/integrations/cdi/oci-objectstorage/src/main/spotbugs/exclusions.xml b/examples/integrations/cdi/oci-objectstorage/src/main/spotbugs/exclusions.xml new file mode 100644 index 00000000000..1bf71bd7d24 --- /dev/null +++ b/examples/integrations/cdi/oci-objectstorage/src/main/spotbugs/exclusions.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/examples/integrations/cdi/pom.xml b/examples/integrations/cdi/pom.xml new file mode 100644 index 00000000000..cca8a77ecb9 --- /dev/null +++ b/examples/integrations/cdi/pom.xml @@ -0,0 +1,80 @@ + + + + 4.0.0 + + io.helidon.integrations.examples + helidon-integrations-examples-project + pom + + Helidon Integrations Examples Project + Helidon Integrations Examples Project + + + io.helidon.examples + helidon-examples-project + 0.10.3-SNAPSHOT + ../../pom.xml + + + + + + ${project.groupId} + helidon-integrations-examples-datasource-hikaricp + ${project.version} + jar + + + ${project.groupId} + helidon-integrations-examples-jedis + ${project.version} + jar + + + ${project.groupId} + helidon-integrations-examples-oci-objectstorage + ${project.version} + jar + + + io.helidon.microprofile.config + helidon-microprofile-config-cdi + jar + ${project.version} + + + io.helidon.microprofile.server + helidon-microprofile-server + jar + ${project.version} + + + + + + datasource-hikaricp + jedis + oci-objectstorage + + + diff --git a/examples/pom.xml b/examples/pom.xml index ab99267a448..6511f6ae946 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -34,5 +34,6 @@ helidon-quickstart-se helidon-quickstart-mp todos + integrations/cdi diff --git a/integrations/README.adoc b/integrations/README.adoc new file mode 100644 index 00000000000..2c8a6549918 --- /dev/null +++ b/integrations/README.adoc @@ -0,0 +1,18 @@ += Helidon Integrations + +The Helidon Integrations project contains adapter code that integrates +various useful external services into Helidon MicroProfile. + +== `serviceconfiguration` + +The `serviceconfiguration` subproject provides mechanisms for +automatically configuring and in some cases provisioning services. + +== `cdi` + +The `cdi` subproject contains CDI +http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#spi[portable +extensions] that integrate various external services into CDI +containers. Some subprojects make use of the `serviceconfiguration` +subproject described above. + diff --git a/integrations/cdi/README.adoc b/integrations/cdi/README.adoc new file mode 100644 index 00000000000..302dac87083 --- /dev/null +++ b/integrations/cdi/README.adoc @@ -0,0 +1,20 @@ += Helidon CDI Integrations + +This subproject contains +http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#spi[CDI portable +extensions] that provide convenient integrations with popular +libraries and services. + +* link:datasource-hikaricp[Datasource using HikariCP]: inject + https://docs.oracle.com/javase/8/docs/api/javax/sql/DataSource.html[`DataSource`] + objects into your CDI-based application that are backed by the + http://brettwooldridge.github.io/HikariCP/[Hikari connection pool]. + +* link:jedis-cdi[Jedis Client]: inject + https://github.com/xetorthio/jedis#jedis[Jedis] + client objects into your CDI-based application. + +* link:oci-objectstorage-cdi[Oracle Cloud Infrastructure Object Storage]: inject an + https://cloud.oracle.com/storage/object-storage/features[OCI Object Storage] + client into your CDI-based application. + diff --git a/integrations/cdi/datasource-hikaricp/pom.xml b/integrations/cdi/datasource-hikaricp/pom.xml new file mode 100644 index 00000000000..f07946c672c --- /dev/null +++ b/integrations/cdi/datasource-hikaricp/pom.xml @@ -0,0 +1,123 @@ + + + + 4.0.0 + + helidon-integrations-cdi-datasource-hikaricp + + Helidon HikariCP DataSource CDI Integration + ${project.name} + + + io.helidon.integrations.cdi + helidon-integrations-cdi-project + 0.10.3-SNAPSHOT + + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + org.jboss.weld.se + weld-se-core + test + + + org.slf4j + slf4j-simple + test + + + + + + io.helidon.serviceconfiguration + helidon-serviceconfiguration-hikaricp-accs + runtime + true + + + io.helidon.serviceconfiguration + helidon-serviceconfiguration-hikaricp-localhost + runtime + true + + + org.jboss + jandex + runtime + true + + + io.helidon.microprofile.config + helidon-microprofile-config-cdi + runtime + true + + + + + + javax.enterprise + cdi-api + provided + + + + + + io.helidon.serviceconfiguration + helidon-serviceconfiguration-config-source + compile + + + io.helidon.serviceconfiguration + helidon-serviceconfiguration-hikaricp + compile + + + com.zaxxer + HikariCP + compile + + + org.eclipse.microprofile.config + microprofile-config-api + compile + + + + + package + + + diff --git a/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/datasource/hikaricp/cdi/HikariCPBackedDataSourceExtension.java b/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/datasource/hikaricp/cdi/HikariCPBackedDataSourceExtension.java new file mode 100644 index 00000000000..574f473206e --- /dev/null +++ b/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/datasource/hikaricp/cdi/HikariCPBackedDataSourceExtension.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.integrations.datasource.hikaricp.cdi; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.literal.NamedLiteral; +import javax.enterprise.inject.spi.AfterBeanDiscovery; +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.enterprise.inject.spi.ProcessInjectionPoint; +import javax.inject.Named; +import javax.sql.DataSource; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigSource; + +/** + * An {@link Extension} that arranges for named {@link DataSource} + * injection points to be satisfied. + */ +public class HikariCPBackedDataSourceExtension implements Extension { + + private final Set dataSourceNames; + + /** + * Creates a new {@link HikariCPBackedDataSourceExtension}. + */ + public HikariCPBackedDataSourceExtension() { + super(); + this.dataSourceNames = new HashSet<>(); + } + + private void processInjectionPoint(@Observes final ProcessInjectionPoint event) { + if (event != null) { + final InjectionPoint injectionPoint = event.getInjectionPoint(); + if (injectionPoint != null) { + final Type type = injectionPoint.getType(); + if (type instanceof Class && DataSource.class.isAssignableFrom((Class) type)) { + final Set qualifiers = injectionPoint.getQualifiers(); + for (final Annotation qualifier : qualifiers) { + if (qualifier instanceof Named) { + final String dataSourceName = ((Named) qualifier).value(); + if (dataSourceName != null && !dataSourceName.isEmpty()) { + this.dataSourceNames.add(dataSourceName); + } + } + } + } + } + } + } + + private void afterBeanDiscovery(@Observes final AfterBeanDiscovery event) { + if (event != null) { + for (final String dataSourceName : this.dataSourceNames) { + event.addBean() + .addQualifier(NamedLiteral.of(dataSourceName)) // ...and Default and Any? + .addTransitiveTypeClosure(HikariDataSource.class) + .beanClass(HikariDataSource.class) + .scope(ApplicationScoped.class) + .produceWith(beans -> new HikariDataSource(new HikariConfig(toProperties(dataSourceName, + beans.select(Config.class).get())))) + .disposeWith((dataSource, beans) -> dataSource.close()); + } + } + } + + private static Properties toProperties(final String dataSourceName, final Config config) { + Objects.requireNonNull(dataSourceName); + Objects.requireNonNull(config); + final Properties returnValue = new Properties(); + + // Look up a required one to bootstrap the whole thing if such + // bootstrapping happens to be necessary. + config.getValue("javax.sql.DataSource." + dataSourceName + ".dataSourceClassName", String.class); + + // The MicroProfile Config specification does not say whether + // property names must be cached or must not be cached + // (https://github.com/eclipse/microprofile-config/issues/370). + // It is implied in the MicroProfile Google group + // (https://groups.google.com/d/msg/microprofile/tvjgSR9qL2Q/M2TNUQrOAQAJ), + // but not in the specification, that ConfigSources can be + // mutable and dynamic. Consequently one would expect their + // property names to come and go. Because of this we have to + // make sure to get all property names from all ConfigSources + // "by hand". + // + // (The MicroProfile Config specification also does not say + // whether a ConfigSource is thread-safe + // (https://github.com/eclipse/microprofile-config/issues/369), + // so iteration over its coming-and-going dynamic property + // names may be problematic, but there's nothing we can do.) + // + // As of this writing, the Helidon MicroProfile Config + // implementation caches all property names up front, which + // may not be correct, but is also not forbidden. + + final Iterable propertyNames = getPropertyNames(config); + if (propertyNames != null) { + final String prefix = "javax.sql.DataSource." + dataSourceName + "."; + final int prefixLength = prefix.length(); + for (final String propertyName : propertyNames) { + if (propertyName != null + && propertyName.length() > prefixLength + && propertyName.startsWith(prefix)) { + returnValue.setProperty(propertyName.substring(prefixLength), config.getValue(propertyName, String.class)); + } + } + } + return returnValue; + } + + private static Set getPropertyNames(final Config config) { + final Set returnValue; + if (config == null) { + returnValue = Collections.emptySet(); + } else { + final Set propertyNames = getPropertyNames(config.getConfigSources()); + if (propertyNames == null || propertyNames.isEmpty()) { + returnValue = Collections.emptySet(); + } else { + returnValue = Collections.unmodifiableSet(propertyNames); + } + } + return returnValue; + } + + private static Set getPropertyNames(final Iterable configSources) { + final Set returnValue = new HashSet<>(); + if (configSources != null) { + for (final ConfigSource configSource : configSources) { + if (configSource != null) { + final Set configSourcePropertyNames = configSource.getPropertyNames(); + if (configSourcePropertyNames != null && !configSourcePropertyNames.isEmpty()) { + returnValue.addAll(configSourcePropertyNames); + } + } + } + } + return Collections.unmodifiableSet(returnValue); + } + +} diff --git a/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/datasource/hikaricp/cdi/config/HikariCP.java b/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/datasource/hikaricp/cdi/config/HikariCP.java new file mode 100644 index 00000000000..325e8a90a4e --- /dev/null +++ b/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/datasource/hikaricp/cdi/config/HikariCP.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.integrations.datasource.hikaricp.cdi.config; + +import io.helidon.service.configuration.api.ServiceConfiguration; +import io.helidon.service.configuration.microprofile.config.ServiceConfigurationConfigSource; + +/** + * A {@link ServiceConfigurationConfigSource} that sits atop the + * {@code hikaricp} {@link ServiceConfiguration} in effect (if there + * is one). + * + * @author Laird Nelson + */ +public final class HikariCP extends ServiceConfigurationConfigSource { + + /** + * Creates a new {@link HikariCP}. + */ + public HikariCP() { + super(); + } + +} diff --git a/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/datasource/hikaricp/cdi/config/package-info.java b/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/datasource/hikaricp/cdi/config/package-info.java new file mode 100644 index 00000000000..e8ca5483138 --- /dev/null +++ b/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/datasource/hikaricp/cdi/config/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides classes and interfaces marrying MicroProfile Config + * constructs and {@link + * io.helidon.service.configuration.api.ServiceConfiguration} + * constructs for the Hikari Connection Pool. + */ +package io.helidon.integrations.datasource.hikaricp.cdi.config; diff --git a/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/datasource/hikaricp/cdi/package-info.java b/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/datasource/hikaricp/cdi/package-info.java new file mode 100644 index 00000000000..f20f96ab4f9 --- /dev/null +++ b/integrations/cdi/datasource-hikaricp/src/main/java/io/helidon/integrations/datasource/hikaricp/cdi/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * CDI integration for the Hikari + * connection pool. + */ +package io.helidon.integrations.datasource.hikaricp.cdi; diff --git a/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/beans.xml b/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..9e3d0906833 --- /dev/null +++ b/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/beans.xml @@ -0,0 +1,25 @@ + + + + diff --git a/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension new file mode 100644 index 00000000000..50f5852d5c8 --- /dev/null +++ b/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -0,0 +1,14 @@ +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +io.helidon.integrations.datasource.hikaricp.cdi.HikariCPBackedDataSourceExtension \ No newline at end of file diff --git a/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource new file mode 100644 index 00000000000..be24ff466e1 --- /dev/null +++ b/integrations/cdi/datasource-hikaricp/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource @@ -0,0 +1,14 @@ +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +io.helidon.integrations.datasource.hikaricp.cdi.config.HikariCP \ No newline at end of file diff --git a/integrations/cdi/jedis-cdi/README.adoc b/integrations/cdi/jedis-cdi/README.adoc new file mode 100644 index 00000000000..b66256fa4ae --- /dev/null +++ b/integrations/cdi/jedis-cdi/README.adoc @@ -0,0 +1,90 @@ += Helidon Jedis CDI Integration + +The Helidon Jedis CDI Integration project supplies a +http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#spi[CDI portable +extension] that lets the end user inject various +https://github.com/xetorthio/jedis#jedis[Jedis] objects into her +CDI-based application. + +== Installation + +Ensure that the Helidon Jedis CDI Integration project and its runtime +dependencies are present on your application's runtime classpath. + +For Maven users, your `` stanza should look like this: + +[source,xml] +---- + + io.helidon.integrations.cdi + helidon-integrations-cdi-jedis + 1.0.0 + runtime + +---- + +== Usage + +If you want to use a +https://static.javadoc.io/redis.clients/jedis/2.9.0/redis/clients/jedis/JedisPool.html[`JedisPool`] +named `orders` in your application code, simply inject it in the +http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#injection_and_resolution[usual, +idiomatic CDI way]. Here is a field injection example: + +[source,java] +---- +@Inject +@Named("orders") +private JedisPool ordersPool; +---- + +And here is a constructor injection example: + +[source,java] +---- +private final JedisPool ordersPool; + +@Inject +public YourConstructor(@Named("orders") JedisPool pool) { + super(); + this.ordersPool = pool; +} +---- + +The Helidon Jedis CDI Integration project will satisfy this injection +point with a +https://static.javadoc.io/redis.clients/jedis/2.9.0/redis/clients/jedis/JedisPool.html[`JedisPool`] +in +http://docs.jboss.org/cdi/api/2.0/javax/enterprise/context/ApplicationScoped.html[application +scope]. + +To create it, the Helidon Jedis CDI Integration project will use +https://static.javadoc.io/org.eclipse.microprofile.config/microprofile-config-api/1.3/index.html?overview-summary.html[MicroProfile +Config] to locate its configuration. +https://static.javadoc.io/org.eclipse.microprofile.config/microprofile-config-api/1.3/org/eclipse/microprofile/config/Config.html#getPropertyNames--[Property +names] that start with `redis.clients.jedis.JedisPool.`_dataSourceName_`.` will +be parsed, and the remaining portion of each such name will be treated +as a Java bean property of `JedisPool`. + +So, for example, a System property with a name like this: + +---- +redis.clients.jedis.JedisPool.orders.port +---- + +...set to a value of: + +---- +6379 +---- + +...together with other similarly-named properties will result +ultimately in a `JedisPool` object injectable under the name `orders` +with a `port` property set to `6379` like so: + +[source,java] +---- +@Inject +@Named("orders") +private JedisPool pool; +---- diff --git a/integrations/cdi/jedis-cdi/pom.xml b/integrations/cdi/jedis-cdi/pom.xml new file mode 100644 index 00000000000..2effb3fe6bf --- /dev/null +++ b/integrations/cdi/jedis-cdi/pom.xml @@ -0,0 +1,108 @@ + + + + 4.0.0 + + helidon-integrations-cdi-jedis + + Helidon Jedis CDI Integration + ${project.name} + + + io.helidon.integrations.cdi + helidon-integrations-cdi-project + 0.10.3-SNAPSHOT + + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.hamcrest + hamcrest-all + test + + + + org.jboss.weld.se + weld-se-core + test + + + + org.slf4j + slf4j-simple + test + + + + + + org.jboss + jandex + runtime + true + + + + io.helidon.microprofile.config + helidon-microprofile-config-cdi + runtime + true + + + + + + javax.enterprise + cdi-api + provided + + + + + + redis.clients + jedis + compile + + + + org.eclipse.microprofile.config + microprofile-config-api + compile + + + + + + package + + + diff --git a/integrations/cdi/jedis-cdi/src/main/java/io/helidon/integrations/jedis/cdi/JedisExtension.java b/integrations/cdi/jedis-cdi/src/main/java/io/helidon/integrations/jedis/cdi/JedisExtension.java new file mode 100644 index 00000000000..fa3b266d9e2 --- /dev/null +++ b/integrations/cdi/jedis-cdi/src/main/java/io/helidon/integrations/jedis/cdi/JedisExtension.java @@ -0,0 +1,534 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.integrations.jedis.cdi; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.CreationException; +import javax.enterprise.inject.Default; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.literal.NamedLiteral; +import javax.enterprise.inject.spi.AfterBeanDiscovery; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.enterprise.inject.spi.ProcessInjectionPoint; +import javax.inject.Named; +import javax.inject.Provider; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; + +import org.eclipse.microprofile.config.Config; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.Protocol; + +/** + * An {@link javax.enterprise.inject.spi.Extension} providing CDI + * integration for the Jedis + * Redis client. + * + * @see Using + * Jedis in a multithreaded environment + */ +public class JedisExtension implements javax.enterprise.inject.spi.Extension { + + private static final Map, Map>> CONVERSION_TYPES = new LinkedHashMap<>(); + + static { + + final Map> jedisPoolConversionTypes = new HashMap<>(); + jedisPoolConversionTypes.put("host", String.class); + jedisPoolConversionTypes.put("port", Integer.class); + jedisPoolConversionTypes.put("connectionTimeout", Integer.class); + jedisPoolConversionTypes.put("socketTimeout", Integer.class); + jedisPoolConversionTypes.put("password", String.class); + jedisPoolConversionTypes.put("database", Integer.class); + jedisPoolConversionTypes.put("clientName", String.class); + jedisPoolConversionTypes.put("ssl", Boolean.class); + CONVERSION_TYPES.put(JedisPool.class, jedisPoolConversionTypes); + + } + + private final Set instanceNames; + + /** + * Creates a new {@link JedisExtension}. + */ + public JedisExtension() { + super(); + this.instanceNames = new HashSet<>(); + } + + private void processJedisInjectionPoint(@Observes final ProcessInjectionPoint e) { + if (e != null) { + final InjectionPoint injectionPoint = e.getInjectionPoint(); + if (injectionPoint != null) { + final Type type = injectionPoint.getType(); + assert type instanceof Class; + assert Jedis.class.isAssignableFrom((Class) type); + final Set qualifiers = injectionPoint.getQualifiers(); + for (final Annotation qualifier : qualifiers) { + final String instanceName; + if (qualifier instanceof Default) { + instanceName = "default"; + } else if (qualifier instanceof Named) { + instanceName = ((Named) qualifier).value(); + } else { + instanceName = null; + } + if (instanceName != null && !instanceName.isEmpty()) { + this.instanceNames.add(instanceName); + } + } + } + } + } + + private > void processJedisProviderInjectionPoint(@Observes final ProcessInjectionPoint e) { + if (e != null) { + final InjectionPoint injectionPoint = e.getInjectionPoint(); + if (injectionPoint != null) { + final Type type = injectionPoint.getType(); + assert type instanceof Class; + assert Provider.class.isAssignableFrom((Class) type); + final Set qualifiers = injectionPoint.getQualifiers(); + for (final Annotation qualifier : qualifiers) { + final String instanceName; + if (qualifier instanceof Default) { + instanceName = "default"; + } else if (qualifier instanceof Named) { + instanceName = ((Named) qualifier).value(); + } else { + instanceName = null; + } + if (instanceName != null && !instanceName.isEmpty()) { + this.instanceNames.add(instanceName); + } + } + } + } + } + + + private void processJedisPoolInjectionPoint(@Observes final ProcessInjectionPoint e) { + if (e != null) { + final InjectionPoint injectionPoint = e.getInjectionPoint(); + if (injectionPoint != null) { + final Type type = injectionPoint.getType(); + assert type instanceof Class; + assert JedisPool.class.isAssignableFrom((Class) type); + final Set qualifiers = injectionPoint.getQualifiers(); + for (final Annotation qualifier : qualifiers) { + final String instanceName; + if (qualifier instanceof Default) { + instanceName = "default"; + } else if (qualifier instanceof Named) { + instanceName = ((Named) qualifier).value(); + } else { + instanceName = null; + } + if (instanceName != null && !instanceName.isEmpty()) { + this.instanceNames.add(instanceName); + } + } + } + } + } + + @SuppressWarnings("checkstyle:linelength") + private > void processJedisPoolProviderInjectionPoint(@Observes final ProcessInjectionPoint e) { + if (e != null) { + final InjectionPoint injectionPoint = e.getInjectionPoint(); + if (injectionPoint != null) { + final Type type = injectionPoint.getType(); + assert type instanceof Class; + assert Provider.class.isAssignableFrom((Class) type); + final Set qualifiers = injectionPoint.getQualifiers(); + for (final Annotation qualifier : qualifiers) { + final String instanceName; + if (qualifier instanceof Default) { + instanceName = "default"; + } else if (qualifier instanceof Named) { + instanceName = ((Named) qualifier).value(); + } else { + instanceName = null; + } + if (instanceName != null && !instanceName.isEmpty()) { + this.instanceNames.add(instanceName); + } + } + } + } + } + + private void processJedisPoolConfigInjectionPoint(@Observes final ProcessInjectionPoint e) { + if (e != null) { + final InjectionPoint injectionPoint = e.getInjectionPoint(); + if (injectionPoint != null) { + final Type type = injectionPoint.getType(); + assert type instanceof Class; + assert JedisPoolConfig.class.isAssignableFrom((Class) type); + final Set qualifiers = injectionPoint.getQualifiers(); + for (final Annotation qualifier : qualifiers) { + final String instanceName; + if (qualifier instanceof Default) { + instanceName = "default"; + } else if (qualifier instanceof Named) { + instanceName = ((Named) qualifier).value(); + } else { + instanceName = null; + } + if (instanceName != null && !instanceName.isEmpty()) { + this.instanceNames.add(instanceName); + } + } + } + } + } + + @SuppressWarnings("checkstyle:linelength") + private > void processJedisPoolConfigProviderInjectionPoint(@Observes final ProcessInjectionPoint e) { + if (e != null) { + final InjectionPoint injectionPoint = e.getInjectionPoint(); + if (injectionPoint != null) { + final Type type = injectionPoint.getType(); + assert type instanceof Class; + assert Provider.class.isAssignableFrom((Class) type); + final Set qualifiers = injectionPoint.getQualifiers(); + for (final Annotation qualifier : qualifiers) { + final String instanceName; + if (qualifier instanceof Default) { + instanceName = "default"; + } else if (qualifier instanceof Named) { + instanceName = ((Named) qualifier).value(); + } else { + instanceName = null; + } + if (instanceName != null && !instanceName.isEmpty()) { + this.instanceNames.add(instanceName); + } + } + } + } + } + + private void addBeans(@Observes final AfterBeanDiscovery event, final BeanManager beanManager) throws IntrospectionException { + if (event != null && beanManager != null) { + for (final String instanceName : this.instanceNames) { + if (instanceName != null) { + + final Set qualifiers; + if (instanceName.equals("default")) { + qualifiers = Collections.singleton(Default.Literal.INSTANCE); + } else { + qualifiers = Collections.singleton(NamedLiteral.of(instanceName)); + } + final Annotation[] qualifiersArray = qualifiers.toArray(new Annotation[qualifiers.size()]); + + event.addBean() + .addTransitiveTypeClosure(JedisPoolConfig.class) + .scope(ApplicationScoped.class) + .addQualifiers(qualifiers) + .produceWith((instance) -> { + final JedisPoolConfig returnValue = new JedisPoolConfig(); + try { + this.configure(instance.select(Config.class).get(), + returnValue, + JedisPoolConfig.class, + instanceName); + } catch (final IntrospectionException | ReflectiveOperationException e) { + throw new CreationException(e.getMessage(), e); + } + return returnValue; + }); + + event.addBean() + .addTransitiveTypeClosure(JedisPool.class) + .scope(ApplicationScoped.class) + .addQualifiers(qualifiers) + .produceWith(instance -> { + return produceJedisPool(instance, instanceName, qualifiersArray); + }) + .disposeWith((p, instance) -> p.destroy()); + + event.addBean() + .addTransitiveTypeClosure(Jedis.class) + .scope(Dependent.class) + .addQualifiers(qualifiers) + .produceWith(instance -> + instance.select(JedisPool.class, qualifiersArray).get().getResource()) + .disposeWith((j, instance) -> j.close()); + } + } + } + } + + private static JedisPool produceJedisPool(final Instance instance, + final String instanceName, + final Annotation[] qualifiersArray) { + final Config config = instance.select(Config.class).get(); + assert config != null; + + final String host = + getPropertyValue(config, JedisPool.class, instanceName, "host", + String.class, Protocol.DEFAULT_HOST); + final Integer port = + getPropertyValue(config, JedisPool.class, instanceName, "port", + Integer.class, Protocol.DEFAULT_PORT); + final Integer connectionTimeout = + getPropertyValue(config, JedisPool.class, instanceName, "connectionTimeout", + Integer.class, Integer.valueOf(Protocol.DEFAULT_TIMEOUT)); + final Integer socketTimeout = + getPropertyValue(config, JedisPool.class, instanceName, "socketTimeout", + Integer.class, Integer.valueOf(Protocol.DEFAULT_TIMEOUT)); + final String password = + getPropertyValue(config, JedisPool.class, instanceName, "password", + String.class, null); + final Integer database = + getPropertyValue(config, JedisPool.class, instanceName, "database", + Integer.class, Protocol.DEFAULT_DATABASE); + final String clientName = + getPropertyValue(config, JedisPool.class, instanceName, "clientName", + String.class, null); + final Boolean ssl = + getPropertyValue(config, JedisPool.class, instanceName, "ssl", + Boolean.class, Boolean.FALSE); + SSLSocketFactory socketFactory = null; + SSLParameters sslParameters = null; + HostnameVerifier hostnameVerifier = null; + if (Boolean.TRUE.equals(ssl)) { + Instance socketFactoriesInstance = + instance.select(SSLSocketFactory.class, qualifiersArray); + if (socketFactoriesInstance.isUnsatisfied()) { + socketFactoriesInstance = + instance.select(SSLSocketFactory.class, Default.Literal.INSTANCE); + if (socketFactoriesInstance.isUnsatisfied()) { + socketFactory = null; + } else { + socketFactory = socketFactoriesInstance.get(); + } + } else { + socketFactory = socketFactoriesInstance.get(); + } + + Instance sslParametersInstance = + instance.select(SSLParameters.class, qualifiersArray); + if (sslParametersInstance.isUnsatisfied()) { + sslParametersInstance = + instance.select(SSLParameters.class, Default.Literal.INSTANCE); + if (sslParametersInstance.isUnsatisfied()) { + sslParameters = null; + } else { + sslParameters = sslParametersInstance.get(); + } + } else { + sslParameters = sslParametersInstance.get(); + } + + Instance hostnameVerifiersInstance = + instance.select(HostnameVerifier.class, qualifiersArray); + if (hostnameVerifiersInstance.isUnsatisfied()) { + hostnameVerifiersInstance = + instance.select(HostnameVerifier.class, Default.Literal.INSTANCE); + if (hostnameVerifiersInstance.isUnsatisfied()) { + hostnameVerifier = null; + } else { + hostnameVerifier = hostnameVerifiersInstance.get(); + } + } else { + hostnameVerifier = hostnameVerifiersInstance.get(); + } + + } + return new JedisPool(instance.select(JedisPoolConfig.class, qualifiersArray).get(), + host, + port.intValue(), + connectionTimeout.intValue(), + socketTimeout.intValue(), + password, + database.intValue(), + clientName, + ssl.booleanValue(), + socketFactory, + sslParameters, + hostnameVerifier); + } + + private static T getPropertyValue(final Config config, + final Class type, + final String instanceName, + final String propertyName, + final Class returnType, + final T defaultValue) { + Objects.requireNonNull(returnType); + T returnValue = null; + if (config != null + && type != null + && propertyName != null) { + + final String configPropertyName; + if (instanceName == null) { + configPropertyName = type.getName() + "." + propertyName; + } else { + configPropertyName = type.getName() + "." + instanceName + "." + propertyName; + } + final Optional value = config.getOptionalValue(configPropertyName, returnType); + if (value != null && value.isPresent()) { + returnValue = value.get(); + } + } + if (returnValue == null) { + returnValue = defaultValue; + } + return returnValue; + } + + /** + * Returns a non-{@code null} {@link Class} representing the type + * to which MicroProfile Config-based conversion of the property + * identified by the supplied {@code name} should occur. + * + * @param type the {@link Class} housing the property identified + * by the supplied {@code name}; must not be {@code null} + * + * @param name the name of a property logically belonging to the + * supplied {@code type}; must not be {@code null} + * + * @return a non-{@code null} {@link Class} representing the type + * to which MicroProfile Config-based conversion of the property + * identified by the supplied {@code name} should occur; {@link + * String String.class} if no conversion type could be found + * + * @exception IntrospectionException if introspection fails; + * overrides are not required to use introspection + * + * @exception NullPointerException if {@code type} or {@code name} + * is {@code null} + */ + protected Class getConversionType(final Class type, final String name) throws IntrospectionException { + Objects.requireNonNull(type); + Objects.requireNonNull(name); + Class returnValue = null; + final Map> conversionTypes = CONVERSION_TYPES.get(type); + if (conversionTypes != null) { + returnValue = conversionTypes.get(name); + } + if (returnValue == null) { + final BeanInfo beanInfo = Introspector.getBeanInfo(type); + if (beanInfo != null) { + final PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); + if (pds != null && pds.length > 0) { + for (final PropertyDescriptor pd : pds) { + if (pd != null && pd.getWriteMethod() != null && name.equals(pd.getName())) { + returnValue = pd.getPropertyType(); + break; + } + } + } + } + } + if (returnValue == null) { + returnValue = String.class; + } + return returnValue; + } + + /** + * Configures the supplied {@link Object} by using the supplied + * {@link Config} in some way. + * + *

This implementation uses {@code java.beans} facilities to + * perform the configuration.

+ * + * @param the type of object to configure + * + * @param config the {@link Config} containing configuration; may + * be {@code null} in which case no action will be taken + * + * @param object the {@link Object} to configure; may be {@code + * null} in which case no action will be taken + * + * @param type one of the ancestral types of the supplied {@code + * object}; must not be {@code null} + * + * @param instanceName the name of the instance to configure; may + * be {@code null} + * + * @exception NullPointerException if {@code type} is {@code null} + * + * @exception IntrospectionException if introspection fails; + * overrides of this method are not required to use introspection + * + * @exception ReflectiveOperationException if there was a problem + * reflecting on the supplied {@link Object}'s {@link + * Object#getClass() Class}; overrides of this method are not + * required to use reflection + */ + protected void configure(final Config config, final T object, final Class type, final String instanceName) + throws IntrospectionException, ReflectiveOperationException { + Objects.requireNonNull(type); + if (config != null && object != null) { + final Class c = object.getClass(); + final BeanInfo beanInfo = Introspector.getBeanInfo(c); + assert beanInfo != null; + final PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); + if (pds != null && pds.length > 0) { + for (final PropertyDescriptor pd : pds) { + if (pd != null) { + final Method writeMethod = pd.getWriteMethod(); + if (writeMethod != null) { + final String propertyName = pd.getName(); + assert propertyName != null; + final String configPropertyName; + if (instanceName == null) { + configPropertyName = type.getName() + "." + propertyName; + } else { + configPropertyName = type.getName() + "." + instanceName + "." + propertyName; + } + final Class conversionType = getConversionType(c, propertyName); + final Optional value = config.getOptionalValue(configPropertyName, conversionType); + if (value != null && value.isPresent()) { + writeMethod.invoke(object, value.get()); + } + } + } + } + } + } + } + +} diff --git a/integrations/cdi/jedis-cdi/src/main/java/io/helidon/integrations/jedis/cdi/package-info.java b/integrations/cdi/jedis-cdi/src/main/java/io/helidon/integrations/jedis/cdi/package-info.java new file mode 100644 index 00000000000..73ca8ac1c97 --- /dev/null +++ b/integrations/cdi/jedis-cdi/src/main/java/io/helidon/integrations/jedis/cdi/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * CDI integration for the Jedis + * Redis client. + */ +package io.helidon.integrations.jedis.cdi; diff --git a/integrations/cdi/jedis-cdi/src/main/resources/META-INF/beans.xml b/integrations/cdi/jedis-cdi/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..9e3d0906833 --- /dev/null +++ b/integrations/cdi/jedis-cdi/src/main/resources/META-INF/beans.xml @@ -0,0 +1,25 @@ + + + + diff --git a/integrations/cdi/jedis-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/integrations/cdi/jedis-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension new file mode 100644 index 00000000000..68fcc2ba1c4 --- /dev/null +++ b/integrations/cdi/jedis-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -0,0 +1,14 @@ +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +io.helidon.integrations.jedis.cdi.JedisExtension \ No newline at end of file diff --git a/integrations/cdi/jedis-cdi/src/test/java/io/helidon/integrations/jedis/cdi/TestExtension.java b/integrations/cdi/jedis-cdi/src/test/java/io/helidon/integrations/jedis/cdi/TestExtension.java new file mode 100644 index 00000000000..3070074e157 --- /dev/null +++ b/integrations/cdi/jedis-cdi/src/test/java/io/helidon/integrations/jedis/cdi/TestExtension.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.integrations.jedis.cdi; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Initialized; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; +import javax.enterprise.inject.spi.CDI; +import javax.inject.Named; + +import org.eclipse.microprofile.config.Config; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.jupiter.api.Assertions.assertAll; + +@ApplicationScoped +class TestExtension { + + private SeContainer cdiContainer; + + TestExtension() { + super(); + } + + @BeforeEach + void startCdiContainer() { + System.setProperty("redis.clients.jedis.JedisPoolConfig.fred.maxIdle", "10"); + final SeContainerInitializer initializer = SeContainerInitializer.newInstance(); + assertThat(initializer, is(notNullValue())); + this.cdiContainer = initializer.initialize(); + } + + @AfterEach + void shutDownCdiContainer() { + if (this.cdiContainer != null) { + this.cdiContainer.close(); + } + } + + private void onStartup(@Observes @Initialized(ApplicationScoped.class) final Object event, + final Config config, + @Named("fred") final JedisPoolConfig fredJedisPoolConfig, + @Named("fred") final JedisPool fredJedisPool) { + assertThat(event, is(notNullValue())); + assertThat(config, is(notNullValue())); + assertThat(fredJedisPoolConfig, is(notNullValue())); + assertThat(fredJedisPoolConfig.getMaxIdle(), is(10)); + assertThat(fredJedisPool, is(notNullValue())); + } + + @Test + void testBeansWereAdded() { + final Instance poolConfigs = CDI.current().select(JedisPoolConfig.class); + assertThat(poolConfigs, is(notNullValue())); + boolean found = false; + for (final JedisPoolConfig poolConfig : poolConfigs) { + found = found || poolConfig != null; + } + assertThat("No JedisPoolConfigs found", found); + } + +} diff --git a/integrations/cdi/jedis-cdi/src/test/resources/META-INF/beans.xml b/integrations/cdi/jedis-cdi/src/test/resources/META-INF/beans.xml new file mode 100644 index 00000000000..9e3d0906833 --- /dev/null +++ b/integrations/cdi/jedis-cdi/src/test/resources/META-INF/beans.xml @@ -0,0 +1,25 @@ + + + + diff --git a/integrations/cdi/oci-objectstorage-cdi/README.adoc b/integrations/cdi/oci-objectstorage-cdi/README.adoc new file mode 100644 index 00000000000..29117b149fe --- /dev/null +++ b/integrations/cdi/oci-objectstorage-cdi/README.adoc @@ -0,0 +1,74 @@ += Helidon OCI Object Storage CDI Integration + +The Helidon OCI Object Storage CDI Integration project supplies a +http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#spi[CDI portable +extension] that lets the end user inject an `ObjectStorage` client +into her CDI application. + +== Installation + +Ensure that the Helidon OCI Object Storage CDI Integration project and +its runtime dependencies are present on your application's runtime +classpath. + +For Maven users, your `` stanza should look like this: + +[source,xml] +---- + + io.helidon.integrations.cdi + helidon-integrations-cdi-oci-objectstorage + 1.0.0 + runtime + +---- + +== Usage + +If you want to use an `ObjectStorage` client +in your application code, simply inject it in the +http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#injection_and_resolution[usual, +idiomatic CDI way]. Here is a field injection example: + +[source,java] +---- +@Inject +private ObjectStorage client; +---- + +And here is a constructor injection example: + +[source,java] +---- +private final ObjectStorage client; + +@Inject +public YourConstructor(@Named("orders") ObjectStorage client) { + super(); + this.client = client; +} +---- + +The Helidon OCI Object Storage CDI Integration project will satisfy +this injection point with an `ObjectStorageClient` in +http://docs.jboss.org/cdi/api/2.0/javax/enterprise/context/ApplicationScoped.html[application +scope]. + +To create it, the Helidon OCI Object Storage CDI Integration project +will use +https://static.javadoc.io/org.eclipse.microprofile.config/microprofile-config-api/1.3/index.html?overview-summary.html[MicroProfile +Config] to locate its configuration. The following +https://static.javadoc.io/org.eclipse.microprofile.config/microprofile-config-api/1.3/org/eclipse/microprofile/config/Config.html#getPropertyNames--[Property +names] will be used to establish a connection to the OCI Object +Storage service: + +. `oci.auth.fingerprint` +. `oci.auth.keyFile` +. `oci.auth.passphraseCharacters` +. `oci.auth.user` +. `oci.auth.tenancy` +. `oci.objectstorage.region` + +These properties are +https://docs.cloud.oracle.com/iaas/Content/API/SDKDocs/javasdk.htm#Configur[documented +in the OCI Object Storage Java SDK documentation]. diff --git a/integrations/cdi/oci-objectstorage-cdi/pom.xml b/integrations/cdi/oci-objectstorage-cdi/pom.xml new file mode 100644 index 00000000000..c223383023d --- /dev/null +++ b/integrations/cdi/oci-objectstorage-cdi/pom.xml @@ -0,0 +1,133 @@ + + + + 4.0.0 + + helidon-integrations-cdi-oci-objectstorage + + Helidon OCI Object Storage CDI Integration + ${project.name} + + + io.helidon.integrations.cdi + helidon-integrations-cdi-project + 0.10.3-SNAPSHOT + + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.hamcrest + hamcrest-all + test + + + + org.jboss.weld.se + weld-se-core + test + + + + org.slf4j + slf4j-simple + test + + + + + + org.jboss + jandex + runtime + true + + + + io.helidon.microprofile.config + helidon-microprofile-config-cdi + runtime + true + + + + org.glassfish.jersey.inject + jersey-hk2 + runtime + + + + + + + javax.enterprise + cdi-api + provided + + + + + + com.oracle.oci.sdk + oci-java-sdk-objectstorage + compile + pom + + + + org.eclipse.microprofile.config + microprofile-config-api + compile + + + + + + + + maven-surefire-plugin + + + ${oci.objectstorage.compartmentId} + ${oci.objectstorage.namespaceName} + ${oci.objectstorage.region} + + + + + + + + package + + + + + + diff --git a/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/MicroProfileConfigAuthenticationDetailsProvider.java b/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/MicroProfileConfigAuthenticationDetailsProvider.java new file mode 100644 index 00000000000..97757381cf4 --- /dev/null +++ b/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/MicroProfileConfigAuthenticationDetailsProvider.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.integrations.cdi.oci.objectstorage; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Objects; + +import com.oracle.bmc.auth.CustomerAuthenticationDetailsProvider; +import org.eclipse.microprofile.config.Config; + +final class MicroProfileConfigAuthenticationDetailsProvider extends CustomerAuthenticationDetailsProvider { + + private final Config config; + + MicroProfileConfigAuthenticationDetailsProvider(final Config config) { + super(); + this.config = Objects.requireNonNull(config); + } + + @Deprecated + @Override + public String getPassPhrase() { + final char[] passphraseCharacters = this.getPassphraseCharacters(); + final String returnValue; + if (passphraseCharacters == null) { + returnValue = null; + } else { + returnValue = String.valueOf(passphraseCharacters); + } + return returnValue; + } + + @Override + public char[] getPassphraseCharacters() { + final String passphraseString = this.config.getOptionalValue("oci.auth.passphraseCharacters", String.class).orElse(null); + final char[] returnValue; + if (passphraseString == null) { + returnValue = null; + } else { + returnValue = passphraseString.toCharArray(); + } + return returnValue; + } + + @Override + public InputStream getPrivateKey() { + final String privateKey = this.config.getOptionalValue("oci.auth.privateKey", String.class) + .orElse(null); + if (privateKey == null || privateKey.trim().isEmpty()) { + final String pemFormattedPrivateKeyFilePath = + this.config.getOptionalValue("oci.auth.keyFile", String.class).orElse("~/.oci/oci_api_key.pem"); + assert pemFormattedPrivateKeyFilePath != null; + try { + return new BufferedInputStream(Files.newInputStream(Paths.get(pemFormattedPrivateKeyFilePath))); + } catch (final IOException ioException) { + throw new RuntimeException(ioException.getMessage(), ioException); + } + } else { + return new BufferedInputStream(new ByteArrayInputStream(privateKey.getBytes(StandardCharsets.UTF_8))); + } + } + + @Override + public String getUserId() { + return this.config.getValue("oci.auth.user", String.class); + } + + @Override + public String getTenantId() { + return this.config.getValue("oci.auth.tenancy", String.class); + } + + @Override + public String getFingerprint() { + return this.config.getValue("oci.auth.fingerprint", String.class); + } + +} diff --git a/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/OCIObjectStorageExtension.java b/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/OCIObjectStorageExtension.java new file mode 100644 index 00000000000..410b92f35fe --- /dev/null +++ b/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/OCIObjectStorageExtension.java @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.integrations.cdi.oci.objectstorage; + +import java.lang.annotation.Annotation; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.AmbiguousResolutionException; +import javax.enterprise.inject.spi.AfterBeanDiscovery; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.enterprise.inject.spi.ProcessInjectionPoint; +import javax.enterprise.inject.spi.ProcessManagedBean; +import javax.enterprise.inject.spi.ProcessProducerField; +import javax.enterprise.inject.spi.ProcessProducerMethod; + +import com.oracle.bmc.auth.AuthenticationDetailsProvider; +import com.oracle.bmc.objectstorage.ObjectStorage; +import com.oracle.bmc.objectstorage.ObjectStorageClient; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; + +/** + * An {@link Extension} that integrates the {@link ObjectStorage} + * interface into CDI-based applications. + * + * @author Laird Nelson + * + * @see com.oracle.bmc.objectstorage.ObjectStorage + */ +public class OCIObjectStorageExtension implements Extension { + + private final Map, Object> objectStorageBeans; + + private final Map, Object> objectStorageClientBuilderBeans; + + private final Map, Object> authenticationDetailsProviderBeans; + + /** + * Creates a new {@link OCIObjectStorageExtension}. + */ + public OCIObjectStorageExtension() { + super(); + this.objectStorageBeans = new HashMap<>(7); + this.objectStorageClientBuilderBeans = new HashMap<>(7); + this.authenticationDetailsProviderBeans = new HashMap<>(7); + } + + private void processObjectStorageInjectionPoints(@Observes final ProcessInjectionPoint event) { + if (event != null) { + final InjectionPoint injectionPoint = event.getInjectionPoint(); + if (injectionPoint != null) { + this.objectStorageBeans.put(injectionPoint.getQualifiers(), null); + } + } + } + + @SuppressWarnings("checkstyle:linelength") + private void processObjectStorageClientBuilderInjectionPoints(@Observes final ProcessInjectionPoint event) { + if (event != null) { + final InjectionPoint injectionPoint = event.getInjectionPoint(); + if (injectionPoint != null) { + this.objectStorageClientBuilderBeans.put(injectionPoint.getQualifiers(), null); + } + } + } + + @SuppressWarnings("checkstyle:linelength") + private void processAuthenticationDetailsProviderInjectionPoints(@Observes final ProcessInjectionPoint event) { + if (event != null) { + final InjectionPoint injectionPoint = event.getInjectionPoint(); + if (injectionPoint != null) { + this.authenticationDetailsProviderBeans.put(injectionPoint.getQualifiers(), null); + } + } + } + + @SuppressWarnings("checkstyle:linelength") + private void processPreexistingAuthenticationDetailsProviderManagedBean(@Observes final ProcessManagedBean event) { + if (event != null) { + final Bean bean = event.getBean(); + assert bean != null; + final Set qualifiers = bean.getQualifiers(); + if (this.authenticationDetailsProviderBeans.containsKey(bean.getQualifiers())) { + throw new AmbiguousResolutionException(); + } + this.authenticationDetailsProviderBeans.put(qualifiers, bean); + } + } + + @SuppressWarnings("checkstyle:linelength") + private void processPreexistingAuthenticationDetailsProviderProducerField(@Observes final ProcessProducerField event) { + if (event != null) { + final Bean bean = event.getBean(); + assert bean != null; + final Set qualifiers = bean.getQualifiers(); + if (this.authenticationDetailsProviderBeans.containsKey(bean.getQualifiers())) { + throw new AmbiguousResolutionException(); + } + this.authenticationDetailsProviderBeans.put(qualifiers, bean); + } + } + + @SuppressWarnings("checkstyle:linelength") + private void processPreexistingAuthenticationDetailsProviderProducerMethod(@Observes final ProcessProducerMethod event) { + if (event != null) { + final Bean bean = event.getBean(); + assert bean != null; + final Set qualifiers = bean.getQualifiers(); + if (this.authenticationDetailsProviderBeans.containsKey(bean.getQualifiers())) { + throw new AmbiguousResolutionException(); + } + this.authenticationDetailsProviderBeans.put(qualifiers, bean); + } + } + + + /* + * ObjectStorageClient.Builder beans. + */ + + + @SuppressWarnings("checkstyle:linelength") + private void processPreexistingObjectStorageClientBuilderManagedBean(@Observes final ProcessManagedBean event) { + if (event != null) { + final Bean bean = event.getBean(); + assert bean != null; + final Set qualifiers = bean.getQualifiers(); + if (this.objectStorageClientBuilderBeans.containsKey(bean.getQualifiers())) { + throw new AmbiguousResolutionException(); + } + this.objectStorageClientBuilderBeans.put(qualifiers, bean); + } + } + + @SuppressWarnings("checkstyle:linelength") + private void processPreexistingObjectStorageClientBuilderProducerField(@Observes final ProcessProducerField event) { + if (event != null) { + final Bean bean = event.getBean(); + assert bean != null; + final Set qualifiers = bean.getQualifiers(); + if (this.objectStorageClientBuilderBeans.containsKey(bean.getQualifiers())) { + throw new AmbiguousResolutionException(); + } + this.objectStorageClientBuilderBeans.put(qualifiers, bean); + } + } + + @SuppressWarnings("checkstyle:linelength") + private void processPreexistingObjectStorageClientBuilderProducerMethod(@Observes final ProcessProducerMethod event) { + if (event != null) { + final Bean bean = event.getBean(); + assert bean != null; + final Set qualifiers = bean.getQualifiers(); + if (this.objectStorageClientBuilderBeans.containsKey(bean.getQualifiers())) { + throw new AmbiguousResolutionException(); + } + this.objectStorageClientBuilderBeans.put(qualifiers, bean); + } + } + + + /* + * ObjectStorage beans. + */ + + + private void processPreexistingObjectStorageManagedBean(@Observes final ProcessManagedBean event) { + if (event != null) { + final Bean bean = event.getBean(); + assert bean != null; + final Set qualifiers = bean.getQualifiers(); + if (this.objectStorageBeans.containsKey(bean.getQualifiers())) { + throw new AmbiguousResolutionException(); + } + this.objectStorageBeans.put(qualifiers, bean); + } + } + + @SuppressWarnings("checkstyle:linelength") + private void processPreexistingObjectStorageProducerField(@Observes final ProcessProducerField event) { + if (event != null) { + final Bean bean = event.getBean(); + assert bean != null; + final Set qualifiers = bean.getQualifiers(); + if (this.objectStorageBeans.containsKey(bean.getQualifiers())) { + throw new AmbiguousResolutionException(); + } + this.objectStorageBeans.put(qualifiers, bean); + } + } + + @SuppressWarnings("checkstyle:linelength") + private void processPreexistingObjectStorageProducerMethod(@Observes final ProcessProducerMethod event) { + if (event != null) { + final Bean bean = event.getBean(); + assert bean != null; + final Set qualifiers = bean.getQualifiers(); + if (this.objectStorageBeans.containsKey(bean.getQualifiers())) { + throw new AmbiguousResolutionException(); + } + this.objectStorageBeans.put(qualifiers, bean); + } + } + + + /* + * Generated beans. + */ + + + private void addBeans(@Observes final AfterBeanDiscovery event, final BeanManager beanManager) { + if (event != null && beanManager != null) { + + // We can't look up Config as a CDI bean here because it is + // illegal to call beanManager.getReference() until + // AfterDeploymentValidation time, by which point it is too late + // to add custom beans. And we don't want to look it up + // expensively inside each bean's create() method. So we do it + // "manually" here. + final Config config = ConfigProvider.getConfig(); + assert config != null; + + if (!this.authenticationDetailsProviderBeans.isEmpty()) { + for (final Entry, ?> adpEntry : this.authenticationDetailsProviderBeans.entrySet()) { + assert adpEntry != null; + if (adpEntry.getValue() == null) { + // There was a qualified or default injection point, but + // no bean to satisfy it. Generate one. + final Set qualifiers = adpEntry.getKey(); + assert qualifiers != null; + assert !qualifiers.isEmpty(); + event.addBean() + .scope(ApplicationScoped.class) + .addTransitiveTypeClosure(MicroProfileConfigAuthenticationDetailsProvider.class) + .beanClass(MicroProfileConfigAuthenticationDetailsProvider.class) + .qualifiers(qualifiers) + .createWith(cc -> new MicroProfileConfigAuthenticationDetailsProvider(config)); + } + } + } + + if (!this.objectStorageClientBuilderBeans.isEmpty()) { + for (final Entry, ?> oscbEntry : this.objectStorageClientBuilderBeans.entrySet()) { + assert oscbEntry != null; + if (oscbEntry.getValue() == null) { + // There was a qualified or default injection point, but + // no bean to satisfy it. Generate one. + final Set qualifiers = oscbEntry.getKey(); + assert qualifiers != null; + assert !qualifiers.isEmpty(); + final Annotation[] qualifiersArray = qualifiers.toArray(new Annotation[qualifiers.size()]); + event.addBean() + .scope(ApplicationScoped.class) + .addTransitiveTypeClosure(ObjectStorageClient.Builder.class) + .beanClass(ObjectStorageClient.Builder.class) + .qualifiers(qualifiers) + .createWith(cc -> { + final ObjectStorageClient.Builder builder = ObjectStorageClient.builder(); + assert builder != null; + // Permit further customization before the bean is actually created + beanManager.getEvent().select(ObjectStorageClient.Builder.class, qualifiersArray).fire(builder); + return builder; + }); + } + } + } + + if (!this.objectStorageBeans.isEmpty()) { + for (final Entry, ?> osbEntry : this.objectStorageBeans.entrySet()) { + assert osbEntry != null; + if (osbEntry.getValue() == null) { + // There was a qualified or default injection point, but + // no bean to satisfy it. Generate one. + final Set qualifiers = osbEntry.getKey(); + assert qualifiers != null; + assert !qualifiers.isEmpty(); + final Annotation[] qualifiersArray = qualifiers.toArray(new Annotation[qualifiers.size()]); + event.addBean() + .scope(ApplicationScoped.class) + .addTransitiveTypeClosure(ObjectStorageClient.class) + .beanClass(ObjectStorageClient.class) + .qualifiers(qualifiers) + .createWith(cc -> { + Set> beans = beanManager.getBeans(ObjectStorageClient.Builder.class, qualifiersArray); + final ObjectStorageClient.Builder builder; + if (beans == null || beans.isEmpty()) { + builder = ObjectStorageClient.builder(); + assert builder != null; + // Permit further customization before the bean is actually created + beanManager.getEvent().select(ObjectStorageClient.Builder.class, qualifiersArray).fire(builder); + } else { + final Bean bean = beanManager.resolve(beans); + assert bean != null; + builder = (ObjectStorageClient.Builder) beanManager.getReference(bean, + ObjectStorageClient.Builder.class, + beanManager.createCreationalContext(bean)); + } + assert builder != null; + + beans = beanManager.getBeans(AuthenticationDetailsProvider.class, qualifiersArray); + final AuthenticationDetailsProvider authProvider; + if (beans == null || beans.isEmpty()) { + authProvider = new MicroProfileConfigAuthenticationDetailsProvider(config); + } else { + final Bean bean = beanManager.resolve(beans); + assert bean != null; + authProvider = + (AuthenticationDetailsProvider) beanManager.getReference(bean, + AuthenticationDetailsProvider.class, + beanManager.createCreationalContext(bean)); + } + assert authProvider != null; + final ObjectStorage objectStorage = builder.build(authProvider); + assert objectStorage != null; + objectStorage.setRegion(config.getValue("oci.objectstorage.region", String.class)); // hack + return objectStorage; + }); + } + } + } + + } + this.authenticationDetailsProviderBeans.clear(); + this.objectStorageBeans.clear(); + this.objectStorageClientBuilderBeans.clear(); + } + +} diff --git a/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/OciConfigConfigSource.java b/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/OciConfigConfigSource.java new file mode 100644 index 00000000000..a78495b3cd5 --- /dev/null +++ b/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/OciConfigConfigSource.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.integrations.cdi.oci.objectstorage; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.spi.ConfigSource; + +/** + * A {@link ConfigSource} implementation that is backed by a {@link + * ConfigFileAuthenticationDetailsProvider}. + */ +public final class OciConfigConfigSource implements ConfigSource { + + private volatile Map properties; + + /** + * Creates a new {@link OciConfigConfigSource}. + */ + public OciConfigConfigSource() { + super(); + } + + /** + * Returns the name of this {@link OciConfigConfigSource}. + * + *

This method never returns {@code null}.

+ * + *

This method returns the same value every time it is + * invoked.

+ * + *

Overrides of this method must not return {@code null}.

+ * + *

Overrides of this method must return the same value every time + * they are invoked.

+ * + *

The default return value of this method is subject to change + * without notice.

+ * + * @return the name of this {@link OciConfigConfigSource}; never + * {@code null} + * + * @see ConfigSource#getName() + */ + @Override + public String getName() { + return ".oci/config"; + } + + /** + * Returns the ordinal of this {@link OciConfigConfigSource}. + * + *

This implementation returns {@code 101}, which will ensure + * values from this {@link ConfigSource} implementation will trump + * those from {@code /META-INF/microprofile-config.properties} but + * none other.

+ * + * @return the ordinal of this {@link OciConfigConfigSource}; {@code + * 101} by default + */ + @Override + public int getOrdinal() { + return 101; // one higher than microprofile-config.properties' ordinal + } + + /** + * Returns a value for the supplied {@code propertyName}, or {@code + * null} if there is no such value. + * + *

This method may return {@code null}.

+ * + * @param propertyName the name of the property for which a value + * should be returned; may be {@code null} in which case {@code + * null} will be returned + * + * @return a value for the supplied {@code propertyName}, or {@code + * null} + */ + @Override + public String getValue(final String propertyName) { + final String returnValue; + if (propertyName == null || propertyName.equals("oci.auth.profile") || propertyName.equals("oci.config.path")) { + returnValue = null; + } else { + Map properties = this.properties; + if (properties == null) { + final Config config = ConfigProvider.getConfig(); + assert config != null; + final String profile = config.getOptionalValue("oci.auth.profile", String.class).orElse("DEFAULT"); + assert profile != null; + final String configFilePath = config.getOptionalValue("oci.config.path", String.class).orElse(null); + final ConfigFileAuthenticationDetailsProvider provider; + ConfigFileAuthenticationDetailsProvider temp = null; + try { + if (configFilePath == null) { + temp = new ConfigFileAuthenticationDetailsProvider(profile); + } else { + temp = new ConfigFileAuthenticationDetailsProvider(configFilePath, profile); + } + } catch (final IOException ioException) { + temp = null; + } finally { + provider = temp; + } + properties = createProperties(provider); + assert properties != null; + this.properties = properties; + } + assert properties != null; + returnValue = properties.get(propertyName); + } + return returnValue; + } + + /** + * Returns a {@link Map} consisting of all property names and their + * values that this {@link OciConfigConfigSource} knows about at the + * time that this method is invoked. + * + *

This method never returns {@code null}.

+ * + *

The returned {@link Map} is {@linkplain + * Collections#unmodifiableMap(Map) immutable} and safe for + * concurrent use by multiple threads.

+ * + *

This method may return different {@link Map} instances when + * invoked at different times.

+ * + *

The returned {@link Map}, if non-{@linkplain Map#isEmpty() + * empty}, is guaranteed to contain at least the following keys:

+ * + *
    + * + *
  • oci.auth.fingerprint
  • + * + *
  • oci.auth.passphraseCharacters
  • + * + *
  • oci.auth.tenancy
  • + * + *
  • oci.auth.user
  • + * + *
+ * + *

The MicroProfile Config specification does not give any + * guidance on whether the return value of an implementation of the + * {@link ConfigSource#getProperties()} method should be immutable + * and/or threadsafe. This implementation returns an {@linkplain + * Collections#unmodifiableMap(Map) immutable Map}.

+ * + * @return a non-{@code null} {@link Map} of properties known to + * this {@link OciConfigConfigSource} + * + * @exception IllegalStateException if there was a problem reading + * the OCI config file + * + * @see ConfigSource#getProperties() + */ + @Override + public Map getProperties() { + Map properties = this.properties; + return properties == null || properties.isEmpty() ? Collections.emptyMap() : properties; + } + + private static Map createProperties(final ConfigFileAuthenticationDetailsProvider provider) { + final Map returnValue; + if (provider == null) { + returnValue = Collections.emptyMap(); + } else { + final Map properties = new HashMap<>(); + properties.put("oci.auth.fingerprint", provider.getFingerprint()); + properties.put("oci.auth.passphraseCharacters", String.valueOf(provider.getPassphraseCharacters())); + properties.put("oci.auth.tenancy", provider.getTenantId()); + properties.put("oci.auth.user", provider.getUserId()); + returnValue = Collections.unmodifiableMap(properties); + } + return returnValue; + } + +} diff --git a/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/package-info.java b/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/package-info.java new file mode 100644 index 00000000000..a3054873bb4 --- /dev/null +++ b/integrations/cdi/oci-objectstorage-cdi/src/main/java/io/helidon/integrations/cdi/oci/objectstorage/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides classes and interfaces that integrate the OCI object + * storage service into CDI 2.0-based applications. + * + * @author Laird Nelson + */ +package io.helidon.integrations.cdi.oci.objectstorage; diff --git a/integrations/cdi/oci-objectstorage-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/integrations/cdi/oci-objectstorage-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension new file mode 100644 index 00000000000..68f602708ed --- /dev/null +++ b/integrations/cdi/oci-objectstorage-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -0,0 +1,14 @@ +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +io.helidon.integrations.cdi.oci.objectstorage.OCIObjectStorageExtension \ No newline at end of file diff --git a/integrations/cdi/oci-objectstorage-cdi/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/integrations/cdi/oci-objectstorage-cdi/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource new file mode 100644 index 00000000000..c966840e6a6 --- /dev/null +++ b/integrations/cdi/oci-objectstorage-cdi/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource @@ -0,0 +1,14 @@ +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +io.helidon.integrations.cdi.oci.objectstorage.OciConfigConfigSource \ No newline at end of file diff --git a/integrations/cdi/oci-objectstorage-cdi/src/test/java/io/helidon/integrations/cdi/oci/objectstorage/TestOCIObjectStorageExtension.java b/integrations/cdi/oci-objectstorage-cdi/src/test/java/io/helidon/integrations/cdi/oci/objectstorage/TestOCIObjectStorageExtension.java new file mode 100644 index 00000000000..3f561b1ac66 --- /dev/null +++ b/integrations/cdi/oci-objectstorage-cdi/src/test/java/io/helidon/integrations/cdi/oci/objectstorage/TestOCIObjectStorageExtension.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.integrations.cdi.oci.objectstorage; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Initialized; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; +import javax.enterprise.inject.spi.CDI; +import javax.inject.Inject; +import javax.inject.Named; + +import com.oracle.bmc.objectstorage.ObjectStorage; +import com.oracle.bmc.objectstorage.ObjectStorageClient; +import com.oracle.bmc.objectstorage.requests.ListBucketsRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +@ApplicationScoped +public class TestOCIObjectStorageExtension { + + private SeContainer cdiContainer; + + TestOCIObjectStorageExtension() { + super(); + } + + @BeforeEach + void startCdiContainer() { + assumeFalse(System.getProperty("oci.objectstorage.compartmentId", "").isEmpty()); + assumeFalse(System.getProperty("oci.objectstorage.namespaceName", "").isEmpty()); + assumeFalse(System.getProperty("oci.objectstorage.region", "").isEmpty()); + final SeContainerInitializer initializer = SeContainerInitializer.newInstance(); + assertNotNull(initializer); + this.cdiContainer = initializer.initialize(); + } + + @AfterEach + void shutDownCdiContainer() { + if (this.cdiContainer != null) { + this.cdiContainer.close(); + } + } + + private void onStartup(@Observes @Initialized(ApplicationScoped.class) final Object event, + final ObjectStorage client) { + assertNotNull(client); + assertNotNull(client.toString()); // dereference the proxy + final ListBucketsRequest request = + ListBucketsRequest.builder() + .compartmentId(System.getProperty("oci.objectstorage.compartmentId")) + .namespaceName(System.getProperty("oci.objectstorage.namespaceName")) + .build(); + client.listBuckets(request); + } + + private void configure(@Observes final ObjectStorageClient.Builder builder) { + assertNotNull(builder); + System.out.println("Configuring builder"); + } + + @Test + void testSpike() { + + } + +} diff --git a/integrations/cdi/oci-objectstorage-cdi/src/test/resources/META-INF/beans.xml b/integrations/cdi/oci-objectstorage-cdi/src/test/resources/META-INF/beans.xml new file mode 100644 index 00000000000..9e3d0906833 --- /dev/null +++ b/integrations/cdi/oci-objectstorage-cdi/src/test/resources/META-INF/beans.xml @@ -0,0 +1,25 @@ + + + + diff --git a/integrations/cdi/pom.xml b/integrations/cdi/pom.xml new file mode 100644 index 00000000000..5e9fc622222 --- /dev/null +++ b/integrations/cdi/pom.xml @@ -0,0 +1,68 @@ + + + + 4.0.0 + + io.helidon.integrations.cdi + helidon-integrations-cdi-project + pom + + Helidon CDI Integrations + Helidon CDI Integrations + + + io.helidon.integrations + helidon-integrations-project + 0.10.3-SNAPSHOT + + + + + + ${project.groupId} + helidon-integrations-cdi-datasource-hikaricp + ${project.version} + jar + + + ${project.groupId} + helidon-integrations-cdi-jedis + ${project.version} + jar + + + io.helidon.serviceconfiguration + helidon-serviceconfiguration-project + ${project.version} + pom + import + + + + + + datasource-hikaricp + jedis-cdi + oci-objectstorage-cdi + + + diff --git a/integrations/pom.xml b/integrations/pom.xml new file mode 100644 index 00000000000..572c74e0af1 --- /dev/null +++ b/integrations/pom.xml @@ -0,0 +1,80 @@ + + + + 4.0.0 + + io.helidon.integrations + helidon-integrations-project + pom + + Helidon Integrations Project + Helidon Integrations Project + + + io.helidon + helidon-project + 0.10.3-SNAPSHOT + + + + serviceconfiguration + cdi + + + + + + io.helidon.microprofile.config + helidon-microprofile-config-cdi + ${project.version} + + + + + + + + + maven-compiler-plugin + + + -Xlint:all + -parameters + + + + + + + + + + + true + true + + + false + + + + diff --git a/integrations/serviceconfiguration/README.adoc b/integrations/serviceconfiguration/README.adoc new file mode 100644 index 00000000000..b68584725fa --- /dev/null +++ b/integrations/serviceconfiguration/README.adoc @@ -0,0 +1,42 @@ += Helidon Service Configuration Framework + +The Helidon Service Configuration Framework abstracts the +configuration of a Java object or set of objects (a service) from the +system-dependent configuration sources it might otherwise need, like particular environment +variables or system properties or configuration files. + +The Helidon Service Configuration Framework is _not_ another general +purpose configuration framework, but exists rather to support such +frameworks. + +For example, an end user running code in Oracle's Application +Container Cloud Service (a "system") may be able to acquire +configuration needed for a service her code uses from certain already +populated, well-known environment variables. On the other hand, if +she runs the same code in Heroku, different environment variables may +be present, and if she runs the same code in a simple container +platform such as Oracle's Kubernetes Engine, no such environment +variables may be present, but relevant configuration files may be. +The Helidon Service Configuration Framework abstracts away and +normalizes such differences so that her code does not have to be aware +of which system it is running on. + +In some cases, this abstraction mechanism can fill in "missing" +configuration information. For example, if it is determined that the +end user's code is running in a local test environment, then perhaps +certain database connectivity information can be synthesized for an +in-memory database whose properties will appear to have been set +automatically. + +The Helidon Service Configuration Framework consists of a core API +subproject (`serviceconfiguration-api`), several implementations of +"systems" (`serviceconfiguration-system-oracle-accs`, +`serviceconfiguration-system-kubernetes`) and several implementations +of configurations for various services on those various systems +(`serviceconfiguration-hikaricp-accs`, +`serviceconfiguration-hikaricp-localhost`). + +There is also a subproject that makes the Helidon Service +Configuration Framework into a +https://static.javadoc.io/org.eclipse.microprofile.config/microprofile-config-api/1.2.1/org/eclipse/microprofile/config/spi/ConfigSource.html[MicroProfile +Config `ConfigSource`] (`serviceconfiguration-config-source`). diff --git a/integrations/serviceconfiguration/pom.xml b/integrations/serviceconfiguration/pom.xml new file mode 100644 index 00000000000..3cc6a25a9a0 --- /dev/null +++ b/integrations/serviceconfiguration/pom.xml @@ -0,0 +1,95 @@ + + + + 4.0.0 + + io.helidon.serviceconfiguration + helidon-serviceconfiguration-project + pom + + Helidon Service Configuration Framework Projects + Helidon Service Configuration Framework Projects + + + io.helidon.integrations + helidon-integrations-project + 0.10.3-SNAPSHOT + + + + + + ${project.groupId} + helidon-serviceconfiguration-api + ${project.version} + jar + + + ${project.groupId} + helidon-serviceconfiguration-config-source + ${project.version} + jar + + + ${project.groupId} + helidon-serviceconfiguration-hikaricp + ${project.version} + jar + + + ${project.groupId} + helidon-serviceconfiguration-hikaricp-accs + ${project.version} + jar + + + ${project.groupId} + helidon-serviceconfiguration-hikaricp-localhost + ${project.version} + jar + + + ${project.groupId} + helidon-serviceconfiguration-system-kubernetes + ${project.version} + jar + + + ${project.groupId} + helidon-serviceconfiguration-system-oracle-accs + ${project.version} + jar + + + + + + serviceconfiguration-api + serviceconfiguration-config-source + serviceconfiguration-hikaricp + serviceconfiguration-hikaricp-accs + serviceconfiguration-hikaricp-localhost + serviceconfiguration-system-kubernetes + serviceconfiguration-system-oracle-accs + + + diff --git a/integrations/serviceconfiguration/serviceconfiguration-api/pom.xml b/integrations/serviceconfiguration/serviceconfiguration-api/pom.xml new file mode 100644 index 00000000000..f9156e28f13 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-api/pom.xml @@ -0,0 +1,36 @@ + + + + 4.0.0 + + helidon-serviceconfiguration-api + + Helidon Service Configuration API + ${project.name} + + + io.helidon.serviceconfiguration + helidon-serviceconfiguration-project + 0.10.3-SNAPSHOT + + + diff --git a/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/api/ServiceConfiguration.java b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/api/ServiceConfiguration.java new file mode 100644 index 00000000000..9b589e40051 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/api/ServiceConfiguration.java @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.api; + +import java.util.Objects; +import java.util.Properties; +import java.util.ServiceConfigurationError; // for javadoc only +import java.util.ServiceLoader; +import java.util.Set; + +/** + * An abstract encapsulation of an automatically discovered + * configuration for a given service. + * + *

For the purposes of configuration, a service is normally + * represented as a client library that connects to a remote provider + * of business value. Typically the client library needs to be + * configured in some way in order to function at all. Instances of + * this class aim to provide such configuration in as automated a + * manner as possible.

+ * + *

{@link ServiceConfiguration} instances are typically produced by + * {@link ServiceConfigurationProvider} instances. Obtaining a {@link + * ServiceConfiguration} by any other means may result in undefined + * behavior.

+ * + * @author Laird Nelson + * + * @see #getInstance(String) + * + * @see ServiceConfigurationProvider + */ +public abstract class ServiceConfiguration { + + + /* + * Static fields. + */ + + + /** + * An {@link Iterable} of {@link ServiceConfiguration} instances + * normally set to the return value of the {@link + * ServiceLoader#load(Class)} method. + * + *

This field may be {@code null}.

+ * + *

This field exists primarily to ensure that the built in {@link + * ServiceLoader} cache of discovered instances is used + * properly.

+ * + * @see #getInstance(String, Properties) + */ + private static volatile Iterable serviceConfigurationProviders; + + + /* + * Instance fields. + */ + + + /** + * The identifier of the service this {@link ServiceConfiguration} + * implementation provides configuration for. + * + *

This field is never {@code null}.

+ * + * @see #ServiceConfiguration(String) + * + * @see #getServiceIdentifier() + */ + private final String serviceIdentifier; + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link ServiceConfiguration}. + */ + private ServiceConfiguration() { + super(); + this.serviceIdentifier = null; + } + + /** + * Creates a new {@link ServiceConfiguration}. + * + * @param serviceIdentifier the identifier of the service this + * {@link ServiceConfiguration} implementation will provide + * configuration for; must not be {@code null} + * + * @exception NullPointerException if {@code serviceIdentifier} is + * {@code null} + * + * @see #getServiceIdentifier() + */ + protected ServiceConfiguration(final String serviceIdentifier) { + super(); + this.serviceIdentifier = Objects.requireNonNull(serviceIdentifier); + } + + + /* + * Instance methods. + */ + + + /** + * Returns the identifier of the service this {@link ServiceConfiguration} + * implementation provides configuration for. + * + *

This method never returns {@code null}.

+ * + * @return the identifier of the service this {@link + * ServiceConfiguration} implementation provides configuration for; + * never {@code null} + * + * @see #ServiceConfiguration(String) + */ + public final String getServiceIdentifier() { + return this.serviceIdentifier; + } + + /** + * Returns an {@linkplain java.util.Collections#unmodifiableSet(Set) + * unmodifiable} and unchanging {@link Set} of {@link String}s + * representing the names of properties whose values may be + * retrieved with the {@link #getProperty(String)} method. + * + *

Implementations of this method must not return {@code + * null}.

+ * + *

Implementations of this method must ensure that the {@link + * Set} returned may be used without the end user having to peform + * explicit synchronization.

+ * + *

Implementations of this method may return the same {@link Set} + * instance with each invocation, or different {@link Set} instances + * with different contents.

+ * + * @return an {@linkplain java.util.Collections#unmodifiableSet(Set) + * unmodifiable} and unchanging {@link Set} of {@link String}s + * representing the names of properties whose values may be + * retrieved with the {@link #getProperty(String)} method + */ + public abstract Set getPropertyNames(); + + /** + * Returns a value for the property described by the supplied {@code + * propertyName}, or {@code null} if no such property value exists. + * + *

This method may return {@code null}.

+ * + * @param propertyName the name of the property whose value should + * be returned; must not be {@code null} + * + * @return a value for the property described by the supplied {@code + * propertyName}, or {@code null} + * + * @see #getProperty(String, String) + */ + public final String getProperty(final String propertyName) { + return this.getProperty(propertyName, null); + } + + /** + * Returns a value for the property described by the supplied {@code + * propertyName}, or the value of the supplied {@code defaultValue} + * parameter if no such property value exists. + * + *

Implementations of this method may return {@code null} if {@code + * defaultValue} is {@code null}.

+ * + *

Implementations of this method may return the same or + * different values for each invocation with the same + * parameters.

+ * + * @param propertyName the name of the property whose value should + * be returned; may be {@code null} in which case the value of the + * supplied {@code defaultValue} parameter will be returned instead + * + * @param defaultValue the value to return if a value for the named + * property could not be found; may be {@code null} + * + * @return a value for the property described by the supplied {@code + * propertyName}, or the value of the supplied {@code defaultValue} + * parameter + */ + public abstract String getProperty(String propertyName, String defaultValue); + + + /* + * Static methods. + */ + + + /** + * Returns the sole {@link ServiceConfiguration} implementation in + * effect for the {@linkplain System#getSystems() current + * Systems} and identified by the supplied {@code + * serviceIdentifier}, if there is one, or {@code null} if + * there is no such {@link ServiceConfiguration}. + * + * @param serviceIdentifier the {@linkplain #getServiceIdentifier() + * service identifier} for which a {@link ServiceConfiguration} + * should be sought; must not be {@code null} + * + * @return the sole {@link ServiceConfiguration} implementation in + * effect for the {@linkplain System#getSystems() current + * Systems} and identified by the supplied {@code + * serviceIdentifier}, if there is one, or {@code null} + * + * @exception NullPointerException if {@code serviceIdentifier} is + * {@code null} + * + * @exception ServiceConfigurationError if there was a problem + * {@linkplain ServiceLoader#load(Class) loading Java services} + * + * @see #getInstance(String, Properties) + */ + public static final ServiceConfiguration getInstance(final String serviceIdentifier) { + return getInstance(serviceIdentifier, null); + } + + /** + * Returns the sole {@link ServiceConfiguration} implementation in + * effect for the {@linkplain System#getSystems() current + * Systems} and identified by the supplied {@code + * serviceIdentifier} and suitable for the supplied {@code + * coordinates}, if there is one, or {@code null} if there + * is no such {@link ServiceConfiguration}. + * + *

This method may—and often does—return {@code + * null}.

+ * + *

While the current implementation of this method performs no + * caching other than that performed as a side effect by {@link + * ServiceLoader} method invocations, caching is deliberately not a + * component of the specification of this method's behavior.

+ * + * @param serviceIdentifier the {@linkplain #getServiceIdentifier() + * service identifier} for which a {@link ServiceConfiguration} + * should be sought; must not be {@code null} + * + * @param coordinates a {@link Properties} object containing + * coordinates that might assist a {@link ServiceConfiguration} + * implementation in implementing its {@link #getPropertyNames()} + * and {@link #getProperty(String)} methods; may be {@code null} + * + * @return the sole {@link ServiceConfiguration} implementation in + * effect for the {@linkplain System#getSystems() current + * Systems} and identified by the supplied {@code + * serviceIdentifier}, if there is one, or {@code null} + * + * @exception NullPointerException if {@code serviceIdentifier} is + * {@code null} + * + * @exception ServiceConfigurationError if there was a problem + * {@linkplain ServiceLoader#load(Class) loading Java services} + */ + public static final ServiceConfiguration getInstance(final String serviceIdentifier, final Properties coordinates) { + Objects.requireNonNull(serviceIdentifier); + ServiceConfiguration returnValue = null; + Iterable serviceConfigurationProviders = ServiceConfiguration.serviceConfigurationProviders; + if (serviceConfigurationProviders == null) { + serviceConfigurationProviders = ServiceLoader.load(ServiceConfigurationProvider.class); + assert serviceConfigurationProviders != null; + ServiceConfiguration.serviceConfigurationProviders = serviceConfigurationProviders; + } + assert serviceConfigurationProviders != null; + final Set systems = System.getSystems(); + for (final ServiceConfigurationProvider serviceConfigurationProvider : serviceConfigurationProviders) { + assert serviceConfigurationProvider != null; + if (serviceIdentifier.equals(serviceConfigurationProvider.getServiceIdentifier())) { + returnValue = serviceConfigurationProvider.buildFor(systems, coordinates); + if (returnValue != null) { + if (!serviceIdentifier.equals(returnValue.getServiceIdentifier())) { + throw new IllegalStateException("!serviceIdentifier.equals(returnValue.getServiceIdentifier()): !\"" + + serviceIdentifier + + "\".equals(\"" + returnValue.getServiceIdentifier() + "\")"); + } + break; + } + } + } + return returnValue; + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/api/ServiceConfigurationProvider.java b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/api/ServiceConfigurationProvider.java new file mode 100644 index 00000000000..dd69edf88d5 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/api/ServiceConfigurationProvider.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.api; + +import java.util.Iterator; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; + +/** + * An abstract factory of {@link ServiceConfiguration} instances. + * + * @author Laird Nelson + * + * @see #buildFor(Set, Properties) + * + * @see ServiceConfiguration + */ +public abstract class ServiceConfigurationProvider { + + + /* + * Instance fields. + */ + + + /** + * The identifier of the service this {@link + * ServiceConfigurationProvider} implementation provides + * configuration for. + * + *

This field is never {@code null}.

+ * + * @see #ServiceConfigurationProvider(String) + * + * @see #getServiceIdentifier() + */ + private final String serviceIdentifier; + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link ServiceConfigurationProvider}. + */ + private ServiceConfigurationProvider() { + super(); + this.serviceIdentifier = null; + } + + /** + * Creates a new {@link ServiceConfigurationProvider}. + * + * @param serviceIdentifier the identifier of the service this + * {@link ServiceConfigurationProvider} implementation will provide + * configuration for; must not be {@code null} + * + * @exception NullPointerException if {@code serviceIdentifier} is + * {@code null} + * + * @see #getServiceIdentifier() + */ + protected ServiceConfigurationProvider(final String serviceIdentifier) { + super(); + this.serviceIdentifier = Objects.requireNonNull(serviceIdentifier); + } + + + /* + * Instance methods. + */ + + + /** + * Returns the identifier of the logical service this {@link + * ServiceConfigurationProvider} implementation provides + * {@link ServiceConfiguration} instances for. + * + *

This method never returns {@code null}.

+ * + *

Repeated invocations of this method will yield identical + * return values.

+ * + * @return the identifier of the logical service this {@link + * ServiceConfigurationProvider} implementation provides {@link + * ServiceConfiguration} instances for; never {@code null} + * + * @see #ServiceConfigurationProvider(String) + */ + public final String getServiceIdentifier() { + return this.serviceIdentifier; + } + + /** + * Given a {@link Set} of {@link System}s and an optional {@link + * Properties} object representing coordinates identifying + * a configuration space in which configuration discovery is to take + * place, returns a new {@link ServiceConfiguration} appropriate for + * the configuration space implied by the supplied parameters, + * or {@code null} if no such {@link ServiceConfiguration} + * is applicable. + * + *

Implementations of this method may—and often + * will—return {@code null}.

+ * + *

Multiple invocations of an implementation of this method must + * result in distinct, though perhaps equal, {@link + * ServiceConfiguration} instances.

+ * + *

Implementations of this method may rely on the fact that all + * members of the supplied {@link Set} of {@link System}s will be + * {@linkplain System#isEnabled() enabled}.

+ * + *

Implementations of this method must not call the {@link + * ServiceConfiguration#getInstance(String, Properties)} method, as an + * infinite loop may result.

+ * + * @param systems a {@link Set} of {@link System}s found to be in + * effect; may be {@code null} + * + * @param coordinates a {@link Properties} object containing hints + * that may help in the implementation of the {@link + * ServiceConfiguration} to be returned; may be {@code null} + * + * @return a {@link ServiceConfiguration} suitable for the + * configuration space implied by the supplied parameters, + * or {@code null} if no such {@link ServiceConfiguration} + * is applicable + * + * @see ServiceConfiguration#getInstance(String, Properties) + */ + public abstract ServiceConfiguration buildFor(Set systems, Properties coordinates); + + + /* + * Static methods. + */ + + + /** + * Given a {@link Set} of {@link System}s, returns the {@link + * System} from that {@link Set} that is deemed to be the + * authoritative system, either because it is the only {@linkplain + * System#isEnabled() enabled} member of the {@link Set} or it is + * the first {@linkplain System#isEnabled() enabled} member of the + * {@link Set} whose {@link System#isAuthoritative()} method returns + * {@code true}, or {@code null} if there is no + * authoritative {@link System} to be returned. + * + *

This method may—and often does—return {@code + * null}.

+ * + *

A {@link System} is deemed to be the authoritative system if + * it {@linkplain System#isEnabled is enabled} and either of the + * following is true:

+ * + *
    + * + *
  • It is the sole member of the supplied {@link Set} of {@link + * System}s.
  • + * + *
  • It {@linkplain System#isAuthoritative() describes itself as + * being authoritative}.
  • + * + *
+ * + * @param systems the {@link System}s to inspect; may be {@code + * null} + * + * @param coordinates a {@link Properties} object containing hints + * that may help in the implementation of this {@link + * ServiceConfigurationProvider} not currently used but reserved for + * future use; may be {@code null}. + * + * @return a {@link System} that is deemed to be authoritative, + * or {@code null} if no such {@link System} could be + * found + */ + protected static final System getAuthoritativeSystem(final Set systems, final Properties coordinates) { + System returnValue = null; + if (systems != null && !systems.isEmpty()) { + final Iterator iterator = systems.iterator(); + if (iterator != null && iterator.hasNext()) { + returnValue = iterator.next(); + if (iterator.hasNext()) { + assert systems.size() > 1; + do { + if (returnValue != null && returnValue.isEnabled() && returnValue.isAuthoritative()) { + break; + } else { + returnValue = iterator.next(); + } + } while (iterator.hasNext()); + } else if (returnValue != null && !returnValue.isEnabled()) { + assert systems.size() == 1; + returnValue = null; + } + } + } + return returnValue; + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/api/System.java b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/api/System.java new file mode 100644 index 00000000000..574db9fef8d --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/api/System.java @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.api; + +import java.util.Collection; // for javadoc only +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.ServiceLoader; +import java.util.Set; + +/** + * A {@linkplain ServiceLoader service provider} that represents some + * kind of system in which the current program is running. + * + *

The meaning of system is deliberately loose. A system + * might be a laptop, a platform-as-a-service such as the Oracle Application + * Container Cloud Service or Heroku, a generic Linux-like + * ecosystem running as part of a Kubernetes cluster, and so on.

+ * + *

{@link System} instances {@linkplain #getenv() provide access to + * their environment} as well as to {@link #getProperties() their + * properties}.

+ * + *

In an arbitrary collection of {@link System} instances, zero or + * one of them may be {@linkplain #isAuthoritative() marked as being + * authoritative}. An {@linkplain #isAuthoritative() authoritative + * System} holds sway, and its {@linkplain + * #getProperties() properties} and {@linkplain #getenv() environment + * values} are to be preferred over all others.

+ * + * @author Laird Nelson + * + * @see #getSystems() + * + * @see #getenv() + * + * @see #getProperties() + * + * @see #isAuthoritative() + */ +public abstract class System { + + + /* + * Instance fields. + */ + + + /** + * The name of this {@link System}. + * + *

This field will never be {@code null}.

+ * + * @see #System(String, boolean) + * + * @see #getName() + */ + private final String name; + + /** + * Whether this {@link System} is authoritative. + * + *

A {@link System} that is authoritative is one whose + * {@linkplain #getenv() environment} and {@linkplain + * #getProperties() properties} are to be preferred over all + * others that may be present.

+ * + * @see #System(String, boolean) + * + * @see #isAuthoritative() + */ + private final boolean authoritative; + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link System}. + * + * @param name the name of the {@link System}; must not be {@code + * null} + * + * @param authoritative whether the {@link System} will be + * {@linkplain #isAuthoritative() authoritative} + * + * @exception NullPointerException if {@code name} is {@code null} + * + * @see #getName() + * + * @see #isAuthoritative() + */ + protected System(final String name, final boolean authoritative) { + super(); + this.name = Objects.requireNonNull(name); + this.authoritative = authoritative; + } + + + /* + * Instance methods. + */ + + + /** + * Returns the name of this {@link System}. + * + *

This method never returns {@code null}.

+ * + *

Multiple invocations of this method will return identical + * {@link String} instances.

+ * + * @return the name of this {@link System}; never {@code null} + * + * @see #System(String, boolean) + */ + public final String getName() { + return this.name; + } + + /** + * Returns {@code true} if this {@link System} is enabled. + * + *

If a {@link System} is enabled, then its {@linkplain #getenv() + * environment values} and {@linkplain #getProperties() properties} + * may be used. If a {@link System} is not enabled, then usage of + * its {@linkplain #getenv() environment values} and {@linkplain + * #getProperties() properties} may result in undefined + * behavior.

+ * + * @return {@code true} if this {@link System} is enabled; {@code + * false} otherwise + * + * @see #System(String, boolean) + */ + public abstract boolean isEnabled(); + + /** + * Returns {@code true} if this {@link System} is + * authoritative. + * + *

If a {@link System} is authoritative, then its {@linkplain + * #getenv() environment values} and {@linkplain #getProperties() + * properties} are to be preferred over any others that might be + * present.

+ * + *

In the presence of an authoritative {@link System}, usage of a + * non-authoritative {@link System} may lead to undefined + * behavior.

+ * + * @return {@code true} if this {@link System} is authoritative; + * {@code false} otherwise + * + * @see #System(String, boolean) + */ + public boolean isAuthoritative() { + return this.authoritative; + } + + /** + * Returns the environment of this {@link System} as a + * non-{@code null}, unchanging and {@linkplain + * Collections#unmodifiableMap(Map) unmodifiable Map}. + * + *

This method never returns {@code null}.

+ * + *

Overrides of this method must not return {@code null}.

+ * + *

Overrides of this method must ensure that the {@link Map} + * returned may be used without requiring the user to perform + * synchronization.

+ * + *

Multiple invocations of this method or any overridden variants + * of it are not guaranteed to return equal or identical {@link Map} + * instances.

+ * + *

The default implementation of this method returns the result + * of invoking {@link java.lang.System#getenv()}.

+ * + * @return the environment of this {@link System} as a + * non-{@code null}, unchanging and {@linkplain + * Collections#unmodifiableMap(Map) unmodifiable Map}; + * never {@code null} + * + * @see #getProperties() + * + * @see java.lang.System#getenv() + */ + public Map getenv() { + return java.lang.System.getenv(); + } + + /** + * Returns the properties of this {@link System} as a non-{@code + * null}, unchanging and unmodifiable {@link Properties} object. + * + *

This method never returns {@code null}.

+ * + *

Overrides of this method must not return {@code null}.

+ * + *

Callers must not mutate the {@link Properties} object that is + * returned. Attempts to do so may result in an {@link + * UnsupportedOperationException}.

+ * + *

Multiple invocations of this method or any overridden variants + * of it are not guaranteed to return equal or identical {@link + * Properties} instances.

+ * + *

The default implementation of this method returns the result + * of invoking {@link java.lang.System#getProperties()}.

+ * + * @return the properties of this {@link System} as a non-{@code + * null}, unchanging and unmodifiable {@link Properties} object; + * never {@code null} + * + * @see #getenv() + * + * @see java.lang.System#getProperties() + */ + public Properties getProperties() { + return java.lang.System.getProperties(); + } + + /** + * Returns a hashcode for this {@link System} that varies with only + * its {@linkplain #getName() name}, {@linkplain #isEnabled() + * enablement} and {@linkplain #isAuthoritative() authority}. + * + * @return a hashcode for this {@link System} + * + * @see #equals(Object) + */ + @Override + public int hashCode() { + int hashCode = 17; + + Object value = this.getName(); + int c = value == null ? 0 : value.hashCode(); + hashCode = 37 * hashCode + c; + + c = this.isEnabled() ? 1 : 0; + hashCode = 37 * hashCode + c; + + c = this.isAuthoritative() ? 1 : 0; + hashCode = 37 * hashCode + c; + + return hashCode; + } + + /** + * Returns {@code true} if this {@link System} is equal to the + * supplied {@link Object}. + * + *

This {@link System} is deemed to be equal to an {@link Object} + * passed to this method if the supplied {@link Object} is an + * instance of {@link System} and {@linkplain #getName() has a name} + * equal to the {@linkplain #getName() name} of this {@link System} + * and {@linkplain #isEnabled() has an enabled status} equal to + * {@linkplain #isEnabled() that of this System} and + * {@linkplain #isAuthoritative() has an authoritative status} equal + * to {@linkplain #isAuthoritative() that of this + * System}.

+ * + * @param other the {@link Object} to test; may be {@code null} in + * which case {@code false} will be returned + * + * @return {@code true} if this {@link System} is equal to the + * supplied {@link Object}; {@code false} otherwise + * + * @see #hashCode() + */ + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } else if (other instanceof System) { + final System her = (System) other; + + final Object name = this.getName(); + if (name == null) { + if (her.getName() != null) { + return false; + } + } else if (!name.equals(her.getName())) { + return false; + } + + return this.isEnabled() && her.isEnabled() && this.isAuthoritative() && her.isAuthoritative(); + } else { + return false; + } + } + + + /* + * Static methods. + */ + + + /** + * Returns a non-{@code null}, unchanging and {@linkplain + * Collections#unmodifiableSet(Set) unmodifiable Set} + * of {@link System} instances as found by the {@linkplain + * ServiceLoader Java service provider mechanism}. + * + *

This method never returns {@code null} but may return an + * {@linkplain Collection#isEmpty() empty Set}.

+ * + *

If one of the {@link System} instances so discovered + * {@linkplain #isAuthoritative() is authoritative}, then it will be + * the only member of the returned {@link Set}.

+ * + *

Multiple invocations of this method are not guaranteed to + * return equal or identical {@link Set} instances.

+ * + * @return a non-{@code null} {@linkplain + * Collections#unmodifiableSet(Set) unmodifiable Set} + * of {@link System} instances as found by the {@linkplain + * ServiceLoader Java service provider mechanism}; never {@code + * null} + * + * @see #isAuthoritative() + * + * @see ServiceLoader#load(Class) + */ + public static final Set getSystems() { + final Iterable systemsIterable = ServiceLoader.load(System.class); + assert systemsIterable != null; + Set returnValue = null; + for (final System system : systemsIterable) { + assert system != null; + if (system.isEnabled()) { + if (system.isAuthoritative()) { + returnValue = Collections.singleton(system); + break; + } else if (returnValue == null) { + returnValue = new HashSet<>(); + returnValue.add(system); + } else { + returnValue.add(system); + break; + } + } + } + if (returnValue == null) { + returnValue = Collections.emptySet(); + } else { + assert !returnValue.isEmpty(); + returnValue = Collections.unmodifiableSet(returnValue); + } + return returnValue; + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/api/package-info.java b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/api/package-info.java new file mode 100644 index 00000000000..403a67fe410 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/api/package-info.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides classes and interfaces for automatically discovering + * service configuration information. + * + * @author Laird Nelson + * + * @see io.helidon.service.configuration.api.ServiceConfiguration + * + * @see io.helidon.service.configuration.api.ServiceConfigurationProvider + * + * @see io.helidon.service.configuration.api.System + */ +package io.helidon.service.configuration.api; diff --git a/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/localhost/LocalhostSystem.java b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/localhost/LocalhostSystem.java new file mode 100644 index 00000000000..8b85f34ca52 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/localhost/LocalhostSystem.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.localhost; + +import io.helidon.service.configuration.api.System; + +/** + * A {@linkplain System#isAuthoritative() non-authoritative} {@link + * System} implementation describing the current local host. + * + * @author Laird Nelson + * + * @see System + */ +public final class LocalhostSystem extends System { + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link LocalhostSystem} {@linkplain #getName() + * named} {@code localhost} that {@linkplain #isEnabled() is + * enabled} and {@linkplain #isAuthoritative() not authoritative}. + * + * @see #isEnabled() + * + * @see #isAuthoritative() + * + * @see System + */ + public LocalhostSystem() { + super("localhost", false); + } + + + /* + * Instance methods. + */ + + + /** + * Returns {@code true} when invoked. + * + * @return {@code true} when invoked + * + * @see System#isEnabled() + */ + @Override + public boolean isEnabled() { + return true; + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/localhost/package-info.java b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/localhost/package-info.java new file mode 100644 index 00000000000..7535c93c885 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/java/io/helidon/service/configuration/localhost/package-info.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides the {@link + * io.helidon.service.configuration.localhost.LocalhostSystem} {@link + * io.helidon.service.configuration.api.System System} implementation. + * + * @author Laird Nelson + * + * @see io.helidon.service.configuration.localhost.LocalhostSystem + * + * @see io.helidon.service.configuration.api.System + */ +package io.helidon.service.configuration.localhost; diff --git a/integrations/serviceconfiguration/serviceconfiguration-api/src/main/javadoc/overview.html b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/javadoc/overview.html new file mode 100644 index 00000000000..c2412250e35 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/javadoc/overview.html @@ -0,0 +1,29 @@ + + +

Provides classes and interfaces for automatically discovering + service configuration information.

+ + @author Laird Nelson + + @see io.helidon.service.configuration.api.ServiceConfiguration + + @see io.helidon.service.configuration.api.ServiceConfigurationProvider + + @see io.helidon.service.configuration.api.System + diff --git a/integrations/serviceconfiguration/serviceconfiguration-api/src/main/resources/META-INF/services/io.helidon.service.configuration.api.System b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/resources/META-INF/services/io.helidon.service.configuration.api.System new file mode 100644 index 00000000000..7d7efec427e --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-api/src/main/resources/META-INF/services/io.helidon.service.configuration.api.System @@ -0,0 +1,14 @@ +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +io.helidon.service.configuration.localhost.LocalhostSystem \ No newline at end of file diff --git a/integrations/serviceconfiguration/serviceconfiguration-config-source/README.adoc b/integrations/serviceconfiguration/serviceconfiguration-config-source/README.adoc new file mode 100644 index 00000000000..b9c1e713462 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-config-source/README.adoc @@ -0,0 +1,20 @@ += Service Configuration `ConfigSource` Implementation + +This project implements a MicroProfile Config `ConfigSource` in terms +of the Helidon Service Configuration API. + +== Installation + +Ensure that the `serviceconfiguration-config-source` artifact and its +dependencies are on your runtime classpath. + +== Usage + +You use the Service Configuration `ConfigSource` Implementation +indirectly: by using the normal +https://javadoc.io/doc/org.eclipse.microprofile.config/microprofile-config-api/1.2.1[MicroProfile +Config APIs]. The presence of the Service Configuration +`ConfigSource` Implementation artifact on your runtime classpath is +enough to cause it to be consulted in the normal course of acquiring +configuration information through the MicroProfil Config API. + diff --git a/integrations/serviceconfiguration/serviceconfiguration-config-source/pom.xml b/integrations/serviceconfiguration/serviceconfiguration-config-source/pom.xml new file mode 100644 index 00000000000..c776dd5d4f1 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-config-source/pom.xml @@ -0,0 +1,49 @@ + + + + 4.0.0 + + helidon-serviceconfiguration-config-source + + Helidon Service Configuration ConfigSource + ${project.name} + + + io.helidon.serviceconfiguration + helidon-serviceconfiguration-project + 0.10.3-SNAPSHOT + + + + + ${project.groupId} + helidon-serviceconfiguration-api + compile + + + org.eclipse.microprofile.config + microprofile-config-api + compile + + + + diff --git a/integrations/serviceconfiguration/serviceconfiguration-config-source/src/main/java/io/helidon/service/configuration/microprofile/config/ServiceConfigurationConfigSource.java b/integrations/serviceconfiguration/serviceconfiguration-config-source/src/main/java/io/helidon/service/configuration/microprofile/config/ServiceConfigurationConfigSource.java new file mode 100644 index 00000000000..d4c8750ecfd --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-config-source/src/main/java/io/helidon/service/configuration/microprofile/config/ServiceConfigurationConfigSource.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.microprofile.config; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import io.helidon.service.configuration.api.ServiceConfiguration; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +/** + * A {@link ConfigSource} implementation that wraps the {@linkplain + * ServiceConfiguration#getInstance(String) + * ServiceConfiguration in effect}. + * + * @author Laird Nelson + * + * @see #getValue(String) + * + * @see #getProperties() + */ +public class ServiceConfigurationConfigSource implements ConfigSource { + + + /* + * Instance fields. + */ + + + /** + * The {@link ServiceConfiguration} this {@link + * ServiceConfigurationConfigSource} wraps. + * + *

This field may be {@code null}.

+ * + * @see ServiceConfiguration#getInstance(String) + */ + private final ServiceConfiguration sc; + + /** + * The service identifier for which a {@linkplain + * ServiceConfiguration#getInstance(String) + * ServiceConfiguration should be retrieved}, and also + * the {@linkplain #getName() name of this + * ConfigSource} implementation. + * + *

This field will never be {@code null}.

+ * + * @see #getName() + */ + private final String name; + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link ServiceConfigurationConfigSource}. + * + *

The name of this {@link ServiceConfigurationConfigSource} will + * be equal to the value of the {@linkplain + * java.lang.System#getProperties() system property} named by + * concatenating this {@link ServiceConfigurationConfigSource}'s + * {@linkplain Class#getSimpleName() simple class name} converted to + * {@linkplain String#toLowerCase() lowercase} with {@code + * .serviceIdentifier}, or, if that is {@code null}, the {@linkplain + * Class#getSimpleName() simple class name} converted to {@linkplain + * String#toLowerCase() lowercase} itself.

+ * + * @see #ServiceConfigurationConfigSource(ServiceConfiguration) + */ + protected ServiceConfigurationConfigSource() { + this(null); + } + + /** + * Creates a new {@link ServiceConfigurationConfigSource}. + * + * @param serviceConfiguration the {@link ServiceConfiguration} this + * {@link ServiceConfigurationConfigSource} will wrap. If this + * parameter is {@code null}, then the service identifier for which + * to {@linkplain ServiceConfiguration#getInstance(String) find} a + * {@link ServiceConfiguration} will be determined by first trying + * to use the value of the {@linkplain + * java.lang.System#getProperties() system property} named by + * concatenating this {@link ServiceConfigurationConfigSource}'s + * {@linkplain Class#getSimpleName() simple class name} converted to + * {@linkplain String#toLowerCase() lowercase} with {@code + * .serviceIdentifier}, and then, if that is {@code null} (as it + * commonly may be) by simply using this {@link + * ServiceConfigurationConfigSource}'s {@linkplain + * Class#getSimpleName() simple class name} converted to {@linkplain + * String#toLowerCase() lowercase}. The result of invoking {@link + * ServiceConfiguration#getInstance(String)} on this value will then + * be used as if it had been passed directly. + * + * @see ServiceConfiguration#getInstance(String) + * + * @see ServiceConfiguration#getServiceIdentifier() + */ + protected ServiceConfigurationConfigSource(final ServiceConfiguration serviceConfiguration) { + super(); + if (serviceConfiguration == null) { + final String lowerCaseSimpleClassName = this.getClass().getSimpleName().toLowerCase(); + this.name = System.getProperty(lowerCaseSimpleClassName + ".serviceIdentifier", lowerCaseSimpleClassName); + this.sc = ServiceConfiguration.getInstance(this.name); + } else { + this.name = serviceConfiguration.getServiceIdentifier(); + this.sc = serviceConfiguration; + } + } + + + /* + * Instance methods. + */ + + + /** + * Returns the name of this {@link + * ServiceConfigurationConfigSource}. + * + *

This method never returns {@code null}.

+ * + * @return the name of this {@link + * ServiceConfigurationConfigSource}; never {@code null} + */ + @Override + public final String getName() { + return this.name; + } + + /** + * Returns all property names known to this {@link + * ServiceConfigurationConfigSource} by returning the result of + * invoking the {@link ServiceConfiguration#getPropertyNames()} + * method on the underlying {@link ServiceConfiguration}. + * + *

This method never returns {@code null}.

+ * + * @return all property names known to this {@link + * ServiceConfigurationConfigSource}; never {@code null} + * + * @see ServiceConfiguration#getPropertyNames() + */ + @Override + public final Set getPropertyNames() { + final Set returnValue; + if (this.sc == null) { + returnValue = Collections.emptySet(); + } else { + final Set scPropertyNames = this.sc.getPropertyNames(); + if (scPropertyNames == null) { + returnValue = Collections.emptySet(); + } else { + returnValue = scPropertyNames; + } + } + return returnValue; + } + + /** + * Returns a {@link Map} representing all properties known to this + * {@link ServiceConfigurationConfigSource}. + * + *

This method never returns {@code null}.

+ * + *

The {@link Map} that is returned is assembled using + * invocations of the {@link + * ServiceConfiguration#getPropertyNames()} and {@link + * ServiceConfiguration#getProperty(String)} methods.

+ * + *

The {@link Map} that is returned is immutable and + * thread-safe.

+ * + * @return a {@link Map} representing all properties known to this + * {@link ServiceConfigurationConfigSource}; never {@code null} + * + * @see ServiceConfiguration#getPropertyNames() + * + * @see ServiceConfiguration#getProperty(String) + */ + @Override + public final Map getProperties() { + final Map returnValue; + if (this.sc == null) { + returnValue = Collections.emptyMap(); + } else { + final Collection propertyNames = this.sc.getPropertyNames(); + if (propertyNames == null || propertyNames.isEmpty()) { + returnValue = Collections.emptyMap(); + } else { + final Map properties = new LinkedHashMap<>(); + for (final String propertyName : propertyNames) { + if (propertyName != null) { + properties.put(propertyName, this.sc.getProperty(propertyName)); + } + } + if (properties.isEmpty()) { + returnValue = Collections.emptyMap(); + } else { + returnValue = Collections.unmodifiableMap(properties); + } + } + } + return returnValue; + } + + /** + * Returns the value of the property identified by the supplied + * {@code name}, or {@code null} if there is no such property or a + * value could not be found for some reason. + * + *

This method may return {@code null}.

+ * + *

This method returns the result of invoking the {@link + * ServiceConfiguration#getProperty(String)} method with the + * supplied {@code name}.

+ * + * @param name the name of the property; may be {@code null} in + * which case {@code null} will be returned + * + * @return a value for the named property, or {@code null} + */ + @Override + public final String getValue(final String name) { + final String returnValue; + if (this.sc == null) { + returnValue = null; + } else { + returnValue = this.sc.getProperty(name); + } + return returnValue; + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-config-source/src/main/java/io/helidon/service/configuration/microprofile/config/package-info.java b/integrations/serviceconfiguration/serviceconfiguration-config-source/src/main/java/io/helidon/service/configuration/microprofile/config/package-info.java new file mode 100644 index 00000000000..2dd7f5c7641 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-config-source/src/main/java/io/helidon/service/configuration/microprofile/config/package-info.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides classes and interfaces that implement the {@link + * org.eclipse.microprofile.config.spi.ConfigSource} interface with + * {@link io.helidon.service.configuration.api.ServiceConfiguration} + * constructs. + * + * @author Laird Nelson + * + * @see + * io.helidon.service.configuration.microprofile.config.ServiceConfigurationConfigSource + */ +package io.helidon.service.configuration.microprofile.config; diff --git a/integrations/serviceconfiguration/serviceconfiguration-config-source/src/main/javadoc/overview.html b/integrations/serviceconfiguration/serviceconfiguration-config-source/src/main/javadoc/overview.html new file mode 100644 index 00000000000..d97b0861467 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-config-source/src/main/javadoc/overview.html @@ -0,0 +1,28 @@ + +--> + +

Provides classes and interfaces for implementing a {@link + org.eclipse.microprofile.config.spi.ConfigSource} on top of a {@link + io.helidon.service.configuration.api.ServiceConfiguration}.

+ + @author Laird Nelson + + @see + io.helidon.service.configuration.microprofile.config.ServiceConfigurationConfigSource + diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/README.adoc b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/README.adoc new file mode 100644 index 00000000000..30f5a7c10d0 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/README.adoc @@ -0,0 +1,19 @@ += Service Configuration for HikariCP on Application Container Cloud Service + +This project implements a Helidon Service Configuration Framework +`ServiceConfiguration` that exposes configuration information suitable +for the http://brettwooldridge.github.io/HikariCP/[Hikari connection +pool] when the system in effect is +https://cloud.oracle.com/acc[Oracle's Application Container Cloud +Service]. + +== Usage + +Ensure that both of the following artifacts are present on your runtime classpath: +. `serviceconfiguration-hikaricp-accs` +. `serviceconfiguration-system-oracle-accs` + +When your program is running on the Application Container Cloud +Service platform, Hikari connection pool information will be made +available to programs using the Helidon Service Configuration +Framework. diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/pom.xml b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/pom.xml new file mode 100644 index 00000000000..9f76f929d28 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/pom.xml @@ -0,0 +1,68 @@ + + + + 4.0.0 + + helidon-serviceconfiguration-hikaricp-accs + + Helidon HikariCP ServiceConfiguration Implementation for Application Container Cloud Service + ${project.name} + + + io.helidon.serviceconfiguration + helidon-serviceconfiguration-project + 0.10.3-SNAPSHOT + + + + + org.junit.jupiter + junit-jupiter-api + test + + + + com.h2database + h2 + test + + + + com.oracle.jdbc + ojdbc8 + runtime + true + + + + mysql + mysql-connector-java + runtime + true + + + + ${project.groupId} + helidon-serviceconfiguration-hikaricp + compile + + + + + diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/main/java/io/helidon/service/configuration/hikaricp/accs/HikariCPServiceConfigurationACCSProvider.java b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/main/java/io/helidon/service/configuration/hikaricp/accs/HikariCPServiceConfigurationACCSProvider.java new file mode 100644 index 00000000000..26372028e88 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/main/java/io/helidon/service/configuration/hikaricp/accs/HikariCPServiceConfigurationACCSProvider.java @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.hikaricp.accs; + +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; + +import javax.sql.DataSource; // for javadoc only + +import io.helidon.service.configuration.api.System; +import io.helidon.service.configuration.hikaricp.HikariCPServiceConfiguration; +import io.helidon.service.configuration.hikaricp.HikariCPServiceConfigurationProvider; + +/** + * A {@link HikariCPServiceConfigurationProvider} that provides {@link + * HikariCPServiceConfiguration} instances when running on the Oracle + * Application Container Cloud Service {@linkplain System system}. + * + * @author Laird Nelson + * + * @see HikariCPServiceConfiguration + * + * @see HikariCPServiceConfigurationProvider + */ +public class HikariCPServiceConfigurationACCSProvider extends HikariCPServiceConfigurationProvider { + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link HikariCPServiceConfigurationACCSProvider}. + */ + public HikariCPServiceConfigurationACCSProvider() { + super(); + } + + + /* + * Instance methods. + */ + + + /** + * Overrides the {@link + * HikariCPServiceConfigurationProvider#appliesTo(Properties, + * System, Properties)} method to return {@code true} if the + * supplied {@link System} {@linkplain System#isEnabled() is + * enabled} and has a {@linkplain System#getName() name} equal to + * {@code accs}, and if its {@linkplain System#getenv() environment} + * contains at least one key starting with either {@code MYSQLCS_} + * or {@code DBAAS_}, and if the {@link + * HikariCPServiceConfigurationProvider#appliesTo(Properties, + * System, Properties)} method also returns {@code true}. + * + * @param properties a {@link Properties} instance that will be used + * as the basis of the {@link HikariCPServiceConfiguration} + * implementation that will be returned by the {@link + * #create(Properties, System, Properties)} method; must not be + * {@code null} + * + * @param system a {@link System} determined to be in effect; may, + * strictly speaking, be {@code null} but ordinarily is non-{@code + * null} and {@linkplain System#isEnabled() enabled} + * + * @param coordinates a {@link Properties} instance representing the + * meta-properties in effect; may be {@code null} + * + * @return {@code true} if this {@link + * HikariCPServiceConfigurationACCSProvider} applies to the + * configuration space implied by the supplied parameters; {@code + * false} otherwise + * + * @exception NullPointerException if {@code properties} is {@code null} + */ + @Override + protected boolean appliesTo(final Properties properties, final System system, final Properties coordinates) { + Objects.requireNonNull(properties); + boolean returnValue = false; + if (system != null && system.isEnabled() && "accs".equalsIgnoreCase(system.getName())) { + final Map env = system.getenv(); + if (env != null && !env.isEmpty()) { + final Set keys = env.keySet(); + if (keys != null && !keys.isEmpty()) { + boolean dbaas = false; + boolean mysqlcs = false; + for (final String key : keys) { + if (key != null) { + if (dbaas) { + if (mysqlcs || key.startsWith("MYSQLCS_")) { + mysqlcs = true; + break; + } + } else if (mysqlcs) { + if (key.startsWith("DBAAS_")) { + dbaas = true; + break; + } + } else if (key.startsWith("DBAAS_")) { + dbaas = true; + } else if (key.startsWith("MYSQLCS_")) { + mysqlcs = true; + } + } + } + if (dbaas) { + if (mysqlcs) { + // We deliberately get out of the business of trying to + // pick among competing service bindings. + returnValue = false; + } else { + returnValue = super.appliesTo(properties, system, coordinates); + } + } else if (mysqlcs) { + returnValue = super.appliesTo(properties, system, coordinates); + } else { + returnValue = false; + } + } + } + } + return returnValue; + } + + /** + * Overrides the {@link + * HikariCPServiceConfigurationProvider#installDataSourceProperties(Properties, + * System, Properties, String)} to install data source-related + * properties discovered in the Oracle + * Application Container Cloud Service environment. + * + *

While reading the documentation for this method, note that the + * Oracle Application Container Cloud Service sets up automatic + * service bindings for only Oracle or MySQL databases.

+ * + *

This method:

+ * + *
    + * + *
  1. Calls the {@link + * HikariCPServiceConfigurationProvider#installDataSourceProperties(Properties, + * System, Properties, String)} method.
  2. + * + *
  3. Checks to see if a property named {@code + * javax.sql.DataSource.}{@code dataSourceName}{@code + * .explicitlyConfigured} has a {@link String} value equal to + * anything other than {@code true}, including {@code null}.
  4. + * + *
  5. If so, and only if {@code dataSourceName} is {@code + * null}, then it {@linkplain + * Properties#setProperty(String, String) sets} certain properties + * on {@code target}.
  6. + * + *
  7. Specifically, if there is a MySQL service binding and not an + * Oracle database service binding, the following properties will be + * set: + * + *
      + * + *
    1. {@code javax.sql.DataSource.dataSourceClassName = + * com.mysql.cj.jdbc.MysqlDataSource}
    2. + * + *
    3. {@code javax.sql.DataSource.dataSource.url = + * jdbc:mysql://${MYSQLCS_CONNECT_STRING}}
    4. + * + *
    5. {@code javax.sql.DataSource.dataSource.description = + * Autodiscovered}
    6. + * + *
    7. {@code javax.sql.DataSource.dataSource.user = + * ${MYSQLCS_USER_NAME}}
    8. + * + *
    9. {@code javax.sql.DataSource.dataSource.password = + * ${MYSQLCS_PASSWORD}}
    10. + * + *
  8. + * + *
  9. If instead there is an Oracle database service binding, but + * not also a MySQL service binding, the following properties will + * be set: + * + *
      + * + *
    1. {@code javax.sql.DataSource.dataSourceClassName = + * oracle.jdbc.pool.OracleDataSource}
    2. + * + *
    3. {@code javax.sql.DataSource.dataSource.url = + * jdbc:oracle:thin:@//${DBAAS_DEFAULT_CONNECT_DESCRIPTOR}}
    4. + * + *
    5. {@code javax.sql.DataSource.dataSource.description = + * Autodiscovered}
    6. + * + *
    7. {@code javax.sql.DataSource.dataSource.user = + * ${DBAAS_USER}}
    8. + * + *
    9. {@code javax.sql.DataSource.dataSource.password = + * ${DBAAS_PASSWORD}}
    10. + * + *
  10. + * + *
+ * + * @param target a {@link Properties} instance that will be used as + * the basis of the {@link HikariCPServiceConfiguration} + * implementation that will be returned by the {@link + * #create(Properties, System, Properties)} method and into which + * properties may be installed; must not be {@code null} + * + * @param system a {@link System} determined to be in effect; may, + * strictly speaking, be {@code null} but ordinarily is non-{@code + * null} and {@linkplain System#isEnabled() enabled} + * + * @param coordinates a {@link Properties} instance representing the + * meta-properties in effect; may be {@code null} + * + * @param dataSourceName the data source name in question; may be + * {@code null} + * + * @exception NullPointerException if {@code target} is {@code null} + * + * @see + * HikariCPServiceConfigurationProvider#installDataSourceProperties(Properties, + * System, Properties, String) + */ + @Override + protected void installDataSourceProperties(final Properties target, + final System system, + final Properties coordinates, + String dataSourceName) { + Objects.requireNonNull(target); + super.installDataSourceProperties(target, system, coordinates, dataSourceName); + + if (!"true".equalsIgnoreCase(this.getDataSourceProperty(target, + system, + coordinates, + dataSourceName, + "explicitlyConfigured"))) { + final String prefix = this.getPrefix(); + assert prefix != null; + assert !prefix.isEmpty(); + + // For future reference, see also: + // https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-configuration-properties.html + // and + // https://docs.oracle.com/database/121/JAJDB/oracle/jdbc/pool/OracleDataSource.html. + + final String dataSourceClassName; + final String description; + final String url; + final String urlValue = getJdbcUrl(system, dataSourceName); + final String user; + final String password; + if (dataSourceName == null) { + dataSourceClassName = prefix + ".dataSourceClassName"; + description = prefix + ".dataSource.description"; + url = prefix + ".dataSource.url"; + user = prefix + ".dataSource.user"; + password = prefix + ".dataSource.password"; + } else { + dataSourceName = dataSourceName.trim(); + if (dataSourceName.isEmpty()) { + dataSourceClassName = prefix + ".dataSourceClassName"; + description = prefix + ".dataSource.description"; + url = prefix + ".dataSource.url"; + user = prefix + ".dataSource.user"; + password = prefix + ".dataSource.password"; + } else { + dataSourceClassName = prefix + "." + dataSourceName + ".dataSourceClassName"; + description = prefix + "." + dataSourceName + ".dataSource.description"; + url = prefix + "." + dataSourceName + ".dataSource.url"; + user = prefix + "." + dataSourceName + ".dataSource.user"; + password = prefix + "." + dataSourceName + ".dataSource.password"; + } + } + + target.setProperty(dataSourceClassName, getDataSourceClassName(urlValue)); + target.setProperty(url, urlValue); + target.setProperty(description, "Autodiscovered"); + target.setProperty(user, getUser(system, dataSourceName)); + target.setProperty(password, getPassword(system, dataSourceName)); + } + } + + /** + * Returns the name of a Java class that implements the {@link + * DataSource} interface that is appropriate for the supplied JDBC + * URL. + * + *

This method may return {@code null}.

+ * + *

Overrides of this method may return {@code null}.

+ * + *

Overrides of this method must return the same value for the + * same input.

+ * + *

This implementation returns {@code + * com.mysql.cj.jdbc.MysqlDataSource} if the supplied {@code + * jdbcUrl} starts with {@code jdbc:mysql:}, and returns {@code + * oracle.jdbc.pool.OracleDataSource} if the supplied {@code + * jdbcUrl} starts with {@code jdbc:oracle:}, and {@code null} in + * all other cases.

+ * + * @param jdbcUrl a JDBC URL in {@link String} form; must not be + * {@code null} + * + * @return a {@link DataSource} implementation class name, or {@code + * null} + * + * @exception NullPointerException if {@code jdbcUrl} is {@code + * null} + */ + protected String getDataSourceClassName(final String jdbcUrl) { + Objects.requireNonNull(jdbcUrl); + String returnValue = null; + if (jdbcUrl.startsWith("jdbc:") && jdbcUrl.length() > "jdbc:".length()) { + final int colonIndex = jdbcUrl.indexOf(':', "jdbc:".length()); + if (colonIndex > 0) { + final String type = jdbcUrl.substring("jdbc:".length(), colonIndex); + assert type != null; + assert !type.isEmpty(); + switch (type) { + case "mysql": + returnValue = "com.mysql.cj.jdbc.MysqlDataSource"; + break; + case "oracle": + returnValue = "oracle.jdbc.pool.OracleDataSource"; + break; + default: + break; + } + } + } + return returnValue; + } + + private static String getUser(final System system, final String suppliedDataSourceName) { + String returnValue = null; + if (system != null) { + + final Map env = system.getenv(); + assert env != null; + + String dataSourceName = null; + if (suppliedDataSourceName != null) { + dataSourceName = suppliedDataSourceName.trim().toUpperCase(); + if (dataSourceName.isEmpty()) { + dataSourceName = null; + } + } + + if (dataSourceName == null) { + if (env.containsKey("DBAAS_USER")) { + if (!env.containsKey("MYSQLCS_USER_NAME")) { + returnValue = env.get("DBAAS_USER"); + } + } else if (env.containsKey("MYSQLCS_USER_NAME")) { + returnValue = env.get("MYSQLCS_USER_NAME"); + } + } else if (env.containsKey("DBAAS_" + dataSourceName + "_USER")) { + if (!env.containsKey("MYSQLCS_" + dataSourceName + "_USER_NAME")) { + returnValue = env.get("DBAAS_" + dataSourceName + "_USER"); + } + } else if (env.containsKey("MYSQLCS_ " + dataSourceName + "_USER_NAME")) { + returnValue = env.get("MYSQLCS_" + dataSourceName + "_USER_NAME"); + } + } + return returnValue; + } + + private static String getPassword(final System system, final String suppliedDataSourceName) { + String returnValue = null; + if (system != null) { + + final Map env = system.getenv(); + assert env != null; + + String dataSourceName = null; + if (suppliedDataSourceName != null) { + dataSourceName = suppliedDataSourceName.trim().toUpperCase(); + if (dataSourceName.isEmpty()) { + dataSourceName = null; + } + } + + if (dataSourceName == null) { + if (env.containsKey("DBAAS_PASSWORD")) { + if (!env.containsKey("MYSQLCS_USER_PASSWORD")) { + returnValue = env.get("DBAAS_PASSWORD"); + } + } else if (env.containsKey("MYSQLCS_USER_PASSWORD")) { + returnValue = env.get("MYSQLCS_USER_PASSWORD"); + } + } else if (env.containsKey("DBAAS_" + dataSourceName + "_PASSWORD")) { + if (!env.containsKey("MYSQLCS_" + dataSourceName + "_USER_PASSWORD")) { + returnValue = env.get("DBAAS_" + dataSourceName + "_PASSWORD"); + } + } else if (env.containsKey("MYSQLCS_ " + dataSourceName + "_USER_PASSWORD")) { + returnValue = env.get("MYSQLCS_" + dataSourceName + "_USER_PASSWORD"); + } + } + return returnValue; + } + + private static String getJdbcUrl(final System system, final String suppliedDataSourceName) { + String returnValue = null; + if (system != null) { + + final Map env = system.getenv(); + assert env != null; + + String dataSourceName = null; + if (suppliedDataSourceName != null) { + dataSourceName = suppliedDataSourceName.trim().toUpperCase(); + if (dataSourceName.isEmpty()) { + dataSourceName = null; + } + } + + if (dataSourceName == null) { + if (env.containsKey("DBAAS_DEFAULT_CONNECT_DESCRIPTOR")) { + if (!env.containsKey("MYSQLCS_CONNECT_STRING")) { + returnValue = "jdbc:oracle:thin:@//" + env.get("DBAAS_DEFAULT_CONNECT_DESCRIPTOR"); + } + } else if (env.containsKey("MYSQLCS_CONNECT_STRING")) { + returnValue = "jdbc:mysql://" + env.get("MYSQLCS_CONNECT_STRING"); + } + } else if (env.containsKey("DBAAS_" + dataSourceName + "_CONNECT_DESCRIPTOR")) { + if (!env.containsKey("MYSQLCS_" + dataSourceName + "_URL")) { + returnValue = "jdbc:oracle:thin:@//" + env.get("DBAAS_" + dataSourceName + "_CONNECT_DESCRIPTOR"); + } + } else if (env.containsKey("MYSQLCS_ " + dataSourceName + "_CONNECT_STRING")) { + returnValue = "jdbc:mysql://" + env.get("MYSQLCS_" + dataSourceName + "_CONNECT_STRING"); + } + + } + return returnValue; + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/main/java/io/helidon/service/configuration/hikaricp/accs/package-info.java b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/main/java/io/helidon/service/configuration/hikaricp/accs/package-info.java new file mode 100644 index 00000000000..33153e89259 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/main/java/io/helidon/service/configuration/hikaricp/accs/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides classes and interfaces for automatically discovering + * service configuration information relevant to Hikari + * connection pool componentry when running on the Oracle + * Application Cloud Container Service system. + * + * @author Laird Nelson + * + * @see + * io.helidon.service.configuration.hikaricp.accs.HikariCPServiceConfigurationACCSProvider + */ +package io.helidon.service.configuration.hikaricp.accs; diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/main/javadoc/overview.html b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/main/javadoc/overview.html new file mode 100644 index 00000000000..4447ae9f557 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/main/javadoc/overview.html @@ -0,0 +1,28 @@ + + +

Provides classes and interfaces for auto-discovering + configuration for the Hikari connection + pool when + running on the Oracle Application Container Cloud Service + system.

+ + @author Laird Nelson + + @see io.helidon.service.configuration.hikaricp.accs.HikariCPServiceConfigurationACCSProvider + diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/main/resources/META-INF/services/io.helidon.service.configuration.api.ServiceConfigurationProvider b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/main/resources/META-INF/services/io.helidon.service.configuration.api.ServiceConfigurationProvider new file mode 100644 index 00000000000..5c73df77d61 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/main/resources/META-INF/services/io.helidon.service.configuration.api.ServiceConfigurationProvider @@ -0,0 +1,14 @@ +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +io.helidon.service.configuration.hikaricp.accs.HikariCPServiceConfigurationACCSProvider \ No newline at end of file diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/test/java/io/helidon/service/configuration/hikaricp/accs/TestPropertiesScenarios.java b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/test/java/io/helidon/service/configuration/hikaricp/accs/TestPropertiesScenarios.java new file mode 100644 index 00000000000..5acab49d142 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-accs/src/test/java/io/helidon/service/configuration/hikaricp/accs/TestPropertiesScenarios.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.hikaricp.accs; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import io.helidon.service.configuration.api.ServiceConfiguration; +import io.helidon.service.configuration.api.System; +import io.helidon.service.configuration.hikaricp.HikariCPServiceConfiguration; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class TestPropertiesScenarios { + + public TestPropertiesScenarios() { + super(); + } + + @Test + public void testBareBones() { + final ServiceConfiguration sc = ServiceConfiguration.getInstance("hikaricp"); + assertNull(sc); + } + + @Test + public void testACCSSystem() { + final Map env = new HashMap<>(); + env.put("MYSQLCS_CONNECT_STRING", "TODO"); + env.put("MYSQLCS_USER_NAME", "sa"); + env.put("MYSQLCS_USER_PASSWORD", "sa"); + final System dummyAccsSystem = new System("accs", true) { + @Override + public final boolean isEnabled() { + return true; + } + + @Override + public final Map getenv() { + return env; + } + }; + final HikariCPServiceConfigurationACCSProvider provider = new HikariCPServiceConfigurationACCSProvider(); + final ServiceConfiguration sc = provider.buildFor(Collections.singleton(dummyAccsSystem), null); + assertNotNull(sc); + assertEquals("jdbc:mysql://TODO", sc.getProperty("javax.sql.DataSource.dataSource.url")); + + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/README.adoc b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/README.adoc new file mode 100644 index 00000000000..7622e45f408 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/README.adoc @@ -0,0 +1,15 @@ += Service Configuration for HikariCP on `localhost` + +This project implements a Helidon Service Configuration Framework +`ServiceConfiguration` that exposes configuration information suitable +for the http://brettwooldridge.github.io/HikariCP/[Hikari connection +pool] when the system in effect is your local unit testing +environment. + +== Usage + +Ensure the `serviceconfiguration-hikaricp-localhost` artifact is +present on your runtime classpath. When your program is running in +your local unit testing environment, Hikari connection pool +information will be made available to programs using the Helidon +Service Configuration Framework. diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/pom.xml b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/pom.xml new file mode 100644 index 00000000000..638cba2b3c0 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/pom.xml @@ -0,0 +1,57 @@ + + + + 4.0.0 + + helidon-serviceconfiguration-hikaricp-localhost + + Helidon HikariCP ServiceConfiguration Implementation for Local Testing + ${project.name} + + + io.helidon.serviceconfiguration + helidon-serviceconfiguration-project + 0.10.3-SNAPSHOT + + + + + org.junit.jupiter + junit-jupiter-api + test + + + + com.h2database + h2 + compile + + + + ${project.groupId} + helidon-serviceconfiguration-hikaricp + compile + + + + + diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/java/io/helidon/service/configuration/hikaricp/localhost/HikariCPServiceConfigurationLocalhost.java b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/java/io/helidon/service/configuration/hikaricp/localhost/HikariCPServiceConfigurationLocalhost.java new file mode 100644 index 00000000000..b7e7dfc5bc6 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/java/io/helidon/service/configuration/hikaricp/localhost/HikariCPServiceConfigurationLocalhost.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.hikaricp.localhost; + +import java.util.Objects; +import java.util.Properties; + +import io.helidon.service.configuration.api.System; +import io.helidon.service.configuration.hikaricp.HikariCPServiceConfiguration; + +/** + * A {@link HikariCPServiceConfiguration} that can dynamically add + * data source properties when they are requested. + * + * @author Laird Nelson + * + * @see #getProperty(String, String) + * + * @see HikariCPServiceConfigurationLocalhostProvider + * + * @see HikariCPServiceConfiguration + */ +public class HikariCPServiceConfigurationLocalhost extends HikariCPServiceConfiguration { + + + /* + * Instance fields. + */ + + + /** + * The {@link HikariCPServiceConfigurationLocalhostProvider} that + * {@linkplain + * io.helidon.service.configuration.hikaricp.HikariCPServiceConfigurationProvider#buildFor(Set, + * Properties) built} this {@link + * HikariCPServiceConfigurationLocalhost}. + * + *

This field is never {@code null}.

+ * + * @see + * #HikariCPServiceConfigurationLocalhost(io.helidon.service.configuration.hikaricp.HikariCPServiceConfigurationLocalhostProvider, + * Properties, System, Properties) + */ + private final HikariCPServiceConfigurationLocalhostProvider provider; + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link HikariCPServiceConfigurationLocalhost}. + * + * @param provider the {@link + * HikariCPServiceConfigurationLocalhostProvider} that {@linkplain + * io.helidon.service.configuration.hikaricp.HikariCPServiceConfigurationProvider#buildFor(Set, + * Properties) is building} this {@link + * HikariCPServiceConfigurationLocalhost}; must not be {@code null} + * + * @param properties a {@link Properties} instance that will be used + * as the basis of this implementation; must not be {@code null} + * + * @param system a {@link System} determined to be in effect; may, + * strictly speaking, be {@code null} but ordinarily is non-{@code + * null} and {@linkplain System#isEnabled() enabled} + * + * @param coordinates a {@link Properties} instance representing the + * meta-properties in effect; may be {@code null} + * + * @exception NullPointerException if {@code provider} or {@code + * properties} is {@code null} + * + * @see HikariCPServiceConfigurationLocalhostProvider + */ + public HikariCPServiceConfigurationLocalhost(final HikariCPServiceConfigurationLocalhostProvider provider, + final Properties properties, + final System system, + final Properties coordinates) { + super(Objects.requireNonNull(properties), system, coordinates); + this.provider = Objects.requireNonNull(provider); + } + + + /* + * Instance methods. + */ + + + /** + * Overrides the {@link + * HikariCPServiceConfiguration#getProperty(String, String)} method + * to return a value for the supplied {@code propertyName}, and, if + * one is not found and the {@code propertyName} parameter value + * starts with {@code javax.sql.Datasource.}, to "just-in-time" + * install certain properties related to the data source in + * question, before attempting its retrieval again. + * + *

This method may return {@code null} if {@code defaultValue} is + * {@code null}.

+ * + *

Overrides of this method may return {@code null}.

+ * + * @param propertyName the name of the property in question; must + * not be {@code null} + * + * @param defaultValue the value to return if all attempts to + * retrieve a property value fail; may be {@code null} + * + * @return a value for the property named by the supplied {@code + * propertyName}, or {@code defaultValue} if no such value exists + * and none could be generated + * + * @exception NullPointerException if {@code propertyName} is {@code + * null} + * + * @see + * HikariCPServiceConfigurationLocalhostProvider#installDataSourceProperties(Properties, + * System, Properties, String) + */ + @Override + public String getProperty(final String propertyName, final String defaultValue) { + String returnValue = this.properties.getProperty(Objects.requireNonNull(propertyName)); + if (returnValue == null) { + final String prefix = this.provider.getPrefix(); + assert prefix != null; + assert !prefix.isEmpty(); + if (propertyName.startsWith(prefix + ".")) { + String dataSourceName = this.provider.extractDataSourceName(propertyName); + if (dataSourceName != null) { + dataSourceName = dataSourceName.trim(); + if (!dataSourceName.isEmpty()) { + this.provider.installDataSourceProperties(this.properties, this.system, this.coordinates, dataSourceName); + returnValue = this.properties.getProperty(propertyName); + } + } + } + } + return returnValue; + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/java/io/helidon/service/configuration/hikaricp/localhost/HikariCPServiceConfigurationLocalhostProvider.java b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/java/io/helidon/service/configuration/hikaricp/localhost/HikariCPServiceConfigurationLocalhostProvider.java new file mode 100644 index 00000000000..393fc59db6b --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/java/io/helidon/service/configuration/hikaricp/localhost/HikariCPServiceConfigurationLocalhostProvider.java @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.hikaricp.localhost; + +import java.util.Objects; +import java.util.Properties; + +import io.helidon.service.configuration.api.System; +import io.helidon.service.configuration.hikaricp.HikariCPServiceConfiguration; +import io.helidon.service.configuration.hikaricp.HikariCPServiceConfigurationProvider; +import io.helidon.service.configuration.localhost.LocalhostSystem; + +/** + * A {@link HikariCPServiceConfigurationProvider} that {@linkplain + * #installDataSourceProperties(Properties, System, Properties, + * String) automatically creates} in-memory + * H2 databases as needed. + * + * @author Laird Nelson + * + * @see #installDataSourceProperties(Properties, System, Properties, String) + * + * @see HikariCPServiceConfigurationLocalhost + * + * @see HikariCPServiceConfigurationProvider + */ +public class HikariCPServiceConfigurationLocalhostProvider extends HikariCPServiceConfigurationProvider { + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link HikariCPServiceConfigurationProvider}. + */ + public HikariCPServiceConfigurationLocalhostProvider() { + super(); + } + + + /* + * Instance methods. + */ + + + /** + * Overrides the {@link + * HikariCPServiceConfigurationProvider#create(Properties, System, + * Properties)} method to return a new {@link + * HikariCPServiceConfigurationLocalhost} instance when invoked. + * + *

This method never returns {@code null}.

+ * + *

Overrides of this method must not return {@code null}.

+ * + *

This method returns a new {@link + * HikariCPServiceConfigurationLocalhost} instance with each + * invocation.

+ * + *

Overrides of this method must return a new {@link + * HikariCPServiceConfiguration} implementation of some kind.

+ * + * @param properties a {@link Properties} instance that will be used + * as the basis of the {@link HikariCPServiceConfigurationLocalhost} + * implementation that will be returned; must not be {@code null} + * + * @param system a {@link System} determined to be in effect; may, + * strictly speaking, be {@code null} but ordinarily is non-{@code + * null} and {@linkplain System#isEnabled() enabled} + * + * @param coordinates a {@link Properties} instance representing the + * meta-properties in effect; may be {@code null} + * + * @return a new {@link HikariCPServiceConfigurationLocalhost} + * instance; never {@code null} + * + * @exception NullPointerException if {@code properties} is {@code + * null} + */ + @Override + protected HikariCPServiceConfiguration create(final Properties properties, final System system, final Properties coordinates) { + return new HikariCPServiceConfigurationLocalhost(this, Objects.requireNonNull(properties), system, coordinates); + } + + /** + * Overrides the {@link + * HikariCPServiceConfigurationProvider#appliesTo(Properties, + * System, Properties)} method to return {@code true} if the + * supplied {@link System} is {@linkplain System#isEnabled() + * enabled} and an instance of {@link LocalhostSystem} and if the + * {@linkplain + * HikariCPServiceConfigurationProvider#appliesTo(Properties, + * System, Properties)} method returns {@code true}. + * + * @param properties a {@link Properties} instance that will be used + * as the basis of the {@link HikariCPServiceConfigurationLocalhost} + * implementation that will be returned by the {@link + * #create(Properties, System, Properties)} method; must not be + * {@code null} + * + * @param system a {@link System} determined to be in effect; may, + * strictly speaking, be {@code null} but ordinarily is non-{@code + * null} and {@linkplain System#isEnabled() enabled} + * + * @param coordinates a {@link Properties} instance representing the + * meta-properties in effect; may be {@code null} + * + * @return {@code true} if this {@link + * HikariCPServiceConfigurationLocalhostProvider} applies to the + * configuration space implied by the supplied parameters; {@code + * false} otherwise + * + * @see HikariCPServiceConfigurationProvider#appliesTo(Properties, + * System, Properties) + */ + @Override + protected boolean appliesTo(final Properties properties, final System system, final Properties coordinates) { + Objects.requireNonNull(properties); + final boolean returnValue = + system instanceof LocalhostSystem && system.isEnabled() && super.appliesTo(properties, system, coordinates); + return returnValue; + } + + /** + * Overrides the {@link + * HikariCPServiceConfigurationProvider#installDataSourceProperties(Properties, + * System, Properties, String)} method to automatically create in-memory + * H2 databases as needed. + * + *

Specifically, this method:

+ * + *
    + * + *
  1. Calls the {@link + * HikariCPServiceConfigurationProvider#installDataSourceProperties(Properties, + * System, Properties, String)} method with the supplied + * parameters.
  2. + * + *
  3. Checks to see if a property named {@code + * javax.sql.DataSource.}{@code dataSourceName}{@code + * .explicitlyConfigured} has a {@link String} value equal to + * anything other than {@code true}, including {@code null}.
  4. + * + *
  5. If so, then it {@linkplain Properties#setProperty(String, + * String) sets} certain properties on {@code target} as + * follows: + * + *
      + * + *
    1. {@code javax.sql.DataSource.}{@code + * dataSourceName}{@code .dataSourceClassName = + * org.h2.jdbcx.JdbcDataSource}
    2. + * + *
    3. {@code javax.sql.DataSource.}{@code + * dataSourceName}{@code .dataSource.description = A local, + * transient, in-memory H2 database}
    4. + * + *
    5. {@code javax.sql.DataSource.}{@code + * dataSourceName}{@code .dataSource.user = sa}
    6. + * + *
    7. {@code javax.sql.DataSource.}{@code + * dataSourceName}{@code .dataSource.password = }
    8. + * + *
    9. {@code javax.sql.DataSource.}{@code + * dataSourceName}{@code .dataSourceUrl = + * jdbc:h2:mem:}{@code dataSourceName}
    10. + * + *
  6. + * + *
+ * + * @param target a {@link Properties} instance that will be used as + * the basis of the {@link HikariCPServiceConfigurationLocalhost} + * implementation that will be returned by the {@link + * #create(Properties, System, Properties)} method and into which + * properties may be installed; must not be {@code null} + * + * @param system a {@link System} determined to be in effect; may, + * strictly speaking, be {@code null} but ordinarily is non-{@code + * null} and {@linkplain System#isEnabled() enabled} + * + * @param coordinates a {@link Properties} instance representing the + * meta-properties in effect; may be {@code null} + * + * @param dataSourceName the data source name in question; may be + * {@code null} + * + * @exception NullPointerException if {@code target} is {@code null} + * + * @see + * HikariCPServiceConfigurationProvider#installDataSourceProperties(Properties, + * System, Properties, String) + */ + @Override + protected void installDataSourceProperties(final Properties target, + final System system, + final Properties coordinates, + String dataSourceName) { + Objects.requireNonNull(target); + super.installDataSourceProperties(target, system, coordinates, dataSourceName); + + if (!"true".equalsIgnoreCase(this.getDataSourceProperty(target, + system, + coordinates, + dataSourceName, + "explicitlyConfigured"))) { + final String prefix = this.getPrefix(); + assert prefix != null; + assert !prefix.isEmpty(); + + final String dataSourceClassName; + final String description; + final String url; + final String urlValue; + final String user; + final String password; + if (dataSourceName == null) { + urlValue = "jdbc:h2:mem:test"; + dataSourceClassName = prefix + ".dataSourceClassName"; + description = prefix + ".dataSource.description"; + url = prefix + ".dataSource.url"; + user = prefix + ".dataSource.user"; + password = prefix + ".dataSource.password"; + } else { + dataSourceName = dataSourceName.trim(); + if (dataSourceName.isEmpty()) { + urlValue = "jdbc:h2:mem:test"; + dataSourceClassName = prefix + ".dataSourceClassName"; + description = prefix + ".dataSource.description"; + url = prefix + ".dataSource.url"; + user = prefix + ".dataSource.user"; + password = prefix + ".dataSource.password"; + } else { + dataSourceClassName = prefix + "." + dataSourceName + ".dataSourceClassName"; + description = prefix + "." + dataSourceName + ".dataSource.description"; + url = prefix + "." + dataSourceName + ".dataSource.url"; + urlValue = "jdbc:h2:mem:" + dataSourceName; + user = prefix + "." + dataSourceName + ".dataSource.user"; + password = prefix + "." + dataSourceName + ".dataSource.password"; + } + } + + target.setProperty(dataSourceClassName, org.h2.jdbcx.JdbcDataSource.class.getName()); + target.setProperty(url, urlValue); + target.setProperty(description, "A local, transient, in-memory H2 database"); + target.setProperty(user, "sa"); + target.setProperty(password, ""); + } + } + + final String extractDataSourceName(final String prefixedProperty) { + String returnValue = null; + if (prefixedProperty != null && !prefixedProperty.isEmpty()) { + final String prefix = this.getPrefix(); + assert prefix != null; + assert !prefix.isEmpty(); + final String prefixWithDot = prefix + "."; + final int prefixWithDotLength = prefixWithDot.length(); + if (prefixedProperty.startsWith(prefixWithDot) && prefixedProperty.length() > prefixWithDotLength) { + final int dotIndex = prefixedProperty.indexOf('.', prefixWithDotLength); + if (dotIndex > 0) { + returnValue = prefixedProperty.substring(prefixWithDotLength, dotIndex); + } + } + } + return returnValue; + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/java/io/helidon/service/configuration/hikaricp/localhost/package-info.java b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/java/io/helidon/service/configuration/hikaricp/localhost/package-info.java new file mode 100644 index 00000000000..de5ce6dfa6d --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/java/io/helidon/service/configuration/hikaricp/localhost/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides classes and interfaces for automatically discovering + * service configuration information relevant to Hikari + * connection pool componentry suitable for local testing + * environments. + * + * @author Laird Nelson + * + * @see io.helidon.service.configuration.hikaricp.localhost.HikariCPServiceConfigurationLocalhost + * + * @see io.helidon.service.configuration.hikaricp.localhost.HikariCPServiceConfigurationLocalhostProvider + */ +package io.helidon.service.configuration.hikaricp.localhost; diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/javadoc/overview.html b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/javadoc/overview.html new file mode 100644 index 00000000000..9224ea10cb3 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/javadoc/overview.html @@ -0,0 +1,28 @@ + + +

Provides classes and interfaces for auto-discovering + configuration for the Hikari connection pool suitable for a local + testing environment.

+ + @author Laird Nelson + + @see io.helidon.service.configuration.hikaricp.localhost.HikariCPServiceConfigurationLocalhost + + @see io.helidon.service.configuration.hikaricp.localhost.HikariCPServiceConfigurationLocalhostProvider + diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/resources/META-INF/services/io.helidon.service.configuration.api.ServiceConfigurationProvider b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/resources/META-INF/services/io.helidon.service.configuration.api.ServiceConfigurationProvider new file mode 100644 index 00000000000..a6b518d25ac --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/main/resources/META-INF/services/io.helidon.service.configuration.api.ServiceConfigurationProvider @@ -0,0 +1,14 @@ +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +io.helidon.service.configuration.hikaricp.localhost.HikariCPServiceConfigurationLocalhostProvider \ No newline at end of file diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/test/java/io/helidon/service/configuration/hikaricp/localhost/TestPropertiesScenarios.java b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/test/java/io/helidon/service/configuration/hikaricp/localhost/TestPropertiesScenarios.java new file mode 100644 index 00000000000..7f3971af035 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp-localhost/src/test/java/io/helidon/service/configuration/hikaricp/localhost/TestPropertiesScenarios.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.hikaricp.localhost; + +import io.helidon.service.configuration.api.ServiceConfiguration; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class TestPropertiesScenarios { + + public TestPropertiesScenarios() { + super(); + } + + @Test + public void testBareBones() { + final ServiceConfiguration sc = ServiceConfiguration.getInstance("hikaricp"); + assertNotNull(sc); + assertEquals("jdbc:h2:mem:test", sc.getProperty("javax.sql.DataSource.dataSource.url")); + } + + @Test + public void testJustInTimePropertyCreation() { + final ServiceConfiguration sc = ServiceConfiguration.getInstance("hikaricp"); + assertNotNull(sc); + assertEquals("jdbc:h2:mem:fred", sc.getProperty("javax.sql.DataSource.fred.dataSource.url")); + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp/README.adoc b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/README.adoc new file mode 100644 index 00000000000..41198cb143f --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/README.adoc @@ -0,0 +1,6 @@ += HikariCP Service Configuration + +This project consists primarily of a `ServiceConfiguration` +implementation for the +https://github.com/brettwooldridge/HikariCP/blob/dev/README.md#-hikaricpits-fasterhikari-hikal%C4%93-origin-japanese-light-ray[Hikari +connection pool]. diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp/pom.xml b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/pom.xml new file mode 100644 index 00000000000..06714589a02 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/pom.xml @@ -0,0 +1,57 @@ + + + + 4.0.0 + + helidon-serviceconfiguration-hikaricp + + Helidon HikariCP ServiceConfiguration Implementation + ${project.name} + + + io.helidon.serviceconfiguration + helidon-serviceconfiguration-project + 0.10.3-SNAPSHOT + + + + + org.junit.jupiter + junit-jupiter-api + test + + + + com.h2database + h2 + test + + + + ${project.groupId} + helidon-serviceconfiguration-api + compile + + + + + diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/main/java/io/helidon/service/configuration/hikaricp/HikariCPServiceConfiguration.java b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/main/java/io/helidon/service/configuration/hikaricp/HikariCPServiceConfiguration.java new file mode 100644 index 00000000000..93a0249ce0b --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/main/java/io/helidon/service/configuration/hikaricp/HikariCPServiceConfiguration.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.hikaricp; + +import java.util.Properties; +import java.util.Set; + +import io.helidon.service.configuration.api.ServiceConfiguration; +import io.helidon.service.configuration.api.ServiceConfigurationProvider; // for javadoc only +import io.helidon.service.configuration.api.System; + +/** + * An abstract {@link ServiceConfiguration} implementation that + * provides configuration information for Hikari + * connection pool componentry. + * + * @author Laird Nelson + * + * @see #HikariCPServiceConfiguration(Properties, System, Properties) + * + * @see HikariCPServiceConfigurationProvider + */ +public class HikariCPServiceConfiguration extends ServiceConfiguration { + + + /* + * Instance fields. + */ + + /** + * A {@link Properties} instance supplied {@linkplain + * #HikariCPServiceConfiguration(Properties, System, Properties) at + * construction time} containing the property values that will + * ultimately be returned by the default implementation of the + * {@link #getPropertyNames()} and {@link #getProperty(String, + * String)} methods. + * + *

This field is never {@code null}.

+ * + * @see #HikariCPServiceConfiguration(Properties, System, + * Properties) + */ + @SuppressWarnings("checkstyle:VisibilityModifier") + protected final Properties properties; + + /** + * The {@link System} that was determined to be the authoritative + * {@link System} at the time this {@link + * HikariCPServiceConfiguration} was {@linkplain + * #HikariCPServiceConfiguration(Properties, System, Properties) + * constructed}. + * + *

This field may be {@code null}.

+ * + * @see #HikariCPServiceConfiguration(Properties, System, + * Properties) + * + * @see ServiceConfigurationProvider#buildFor(Set, Properties) + */ + @SuppressWarnings("checkstyle:VisibilityModifier") + protected final System system; + + /** + * A {@link Properties} instance representing the meta-properties in + * effect at the time this {@link HikariCPServiceConfiguration} was + * {@linkplain #HikariCPServiceConfiguration(Properties, System, + * Properties) constructed}. + * + *

This field may be {@code null}.

+ * + * @see #HikariCPServiceConfiguration(Properties, System, + * Properties) + * + * @see ServiceConfigurationProvider#buildFor(Set, Properties) + */ + @SuppressWarnings("checkstyle:VisibilityModifier") + protected final Properties coordinates; + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link HikariCPServiceConfiguration}. + * + * @param properties a {@link Properties} instance containing the + * property values that will ultimately be returned by the default + * implementation of the {@link #getPropertyNames()} and {@link + * #getProperty(String, String)} methods; may be {@code null} + * + * @param system the {@link System} that was determined to be the + * authoritative {@link System}; may be {@code null} + * + * @param coordinates a {@link Properties} instance representing the + * meta-properties in effect; may be {@code null} + * + * @see ServiceConfigurationProvider#buildFor(Set, Properties) + * + * @see ServiceConfigurationProvider#getAuthoritativeSystem(Set, Properties) + */ + protected HikariCPServiceConfiguration(final Properties properties, final System system, final Properties coordinates) { + super("hikaricp"); + if (properties == null) { + this.properties = new Properties(); + } else { + this.properties = properties; + } + this.system = system; + this.coordinates = coordinates; + } + + + /* + * Instance methods. + */ + + + /** + * Returns an {@linkplain java.util.Collections#unmodifiableSet(Set) + * unmodifiable} and unchanging {@link Set} of {@link String}s + * representing the names of properties whose values may be + * retrieved with the {@link #getProperty(String, String)} method. + * + *

This method never returns {@code null}.

+ * + *

Overrides of this method must not return {@code null}.

+ * + *

Overrides of this method must ensure that the {@link Set} + * returned may be used without the end user having to peform + * explicit synchronization.

+ * + *

This method and its overrides, if any, may return the same + * {@link Set} instance with each invocation, or different {@link + * Set} instances with different contents.

+ * + * @return an {@linkplain java.util.Collections#unmodifiableSet(Set) + * unmodifiable} and unchanging {@link Set} of {@link String}s + * representing the names of properties whose values may be + * retrieved with the {@link #getProperty(String, String)} method + * + * @see #getProperty(String, String) + */ + @Override + public Set getPropertyNames() { + return this.properties.stringPropertyNames(); + } + + /** + * Returns a value for the property described by the supplied {@code + * propertyName}, or the value of the supplied {@code defaultValue} + * parameter if no such property value exists. + * + *

This method will return {@code null} if {@code defaultValue} + * is {@code null}. + * + *

Overrides of this method may return {@code null} if {@code + * defaultValue} is {@code null}.

+ * + *

This method and its overrides, if any, may return the same or + * different values for each invocation with the same + * parameters.

+ * + * @param propertyName the name of the property whose value should + * be returned; may be {@code null} in which case the value of the + * supplied {@code defaultValue} parameter will be returned instead + * + * @param defaultValue the value to return if a value for the named + * property could not be found; may be {@code null} + * + * @return a value for the property described by the supplied {@code + * propertyName}, or the value of the supplied {@code defaultValue} + * parameter + * + * @see #getPropertyNames() + */ + @Override + public String getProperty(final String propertyName, final String defaultValue) { + return this.properties.getProperty(propertyName, defaultValue); + } + + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/main/java/io/helidon/service/configuration/hikaricp/HikariCPServiceConfigurationProvider.java b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/main/java/io/helidon/service/configuration/hikaricp/HikariCPServiceConfigurationProvider.java new file mode 100644 index 00000000000..872f6ecf15e --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/main/java/io/helidon/service/configuration/hikaricp/HikariCPServiceConfigurationProvider.java @@ -0,0 +1,614 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.hikaricp; + +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; +import java.util.regex.Pattern; + +import io.helidon.service.configuration.api.ServiceConfiguration; +import io.helidon.service.configuration.api.ServiceConfigurationProvider; +import io.helidon.service.configuration.api.System; + +/** + * An abstract {@link ServiceConfigurationProvider} implementation + * that provides {@link HikariCPServiceConfiguration} instances. + * + * @author Laird Nelson + * + * @see #buildFor(Set, Properties) + * + * @see HikariCPServiceConfiguration + */ +public abstract class HikariCPServiceConfigurationProvider extends ServiceConfigurationProvider { + + + /* + * Static fields. + */ + + + /** + * A {@link Pattern} used to split + * whitespace-and-comma-separated—or just + * comma-separated—tokens from a {@link String}. + * + *

This field is never {@code null}.

+ */ + private static final Pattern WHITESPACE_COMMA_PATTERN = Pattern.compile("\\s*,\\s*"); + + + /* + * Instance fields. + */ + + + /** + * The prefix with which relevant property names will start. + * + *

This field is never {@code null}.

+ * + * @see #getPrefix() + */ + private final String prefix; + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link HikariCPServiceConfigurationProvider} whose + * {@linkplain + * ServiceConfigurationProvider#ServiceConfigurationProvider(String) + * service identifier} is {@code hikaricp}. + */ + protected HikariCPServiceConfigurationProvider() { + super("hikaricp"); + this.prefix = "javax.sql.DataSource"; + } + + + /* + * Instance methods. + */ + + + /** + * Returns the prefix with which relevant property names will start. + * + *

This method never returns {@code null}.

+ * + *

Overrides of this method must not return {@code null}.

+ * + *

The default implementation of this method returns {@code + * javax.sql.DataSource}.

+ * + *

Undefined behavior will result if the return value of an + * override of this method is {@linkplain String#isEmpty() + * empty}.

+ * + * @return the non-{@code null} {@code prefix} with which relevant + * property names will start + */ + public String getPrefix() { + return this.prefix; + } + + /** + * Creates and returns a new {@link HikariCPServiceConfiguration}. + * + *

Normally this method is invoked as a result of an invocation + * of the {@link #buildFor(Set, Properties)} method. In these + * cases, the {@link #appliesTo(Properties, System, Properties)} + * method will have already been invoked and will have returned + * {@code true}.

+ * + *

Overrides of this method must not call the {@link + * #buildFor(Set, Properties)} method, as an infinite loop may + * result.

+ * + * @param properties a {@link Properties} instance that can be used + * as the basis of a {@link HikariCPServiceConfiguration} + * implementation; must not be {@code null} + * + * @param system a {@link System} determined to be in effect; may, + * strictly speaking, be {@code null} but ordinarily is non-{@code + * null} and {@linkplain System#isEnabled() enabled} + * + * @param coordinates a {@link Properties} instance representing the + * meta-properties in effect; may be {@code null} + * + * @return a new, non-{@code null} {@link HikariCPServiceConfiguration} + * + * @exception NullPointerException if {@code properties} is {@code + * null} + * + * @see + * HikariCPServiceConfiguration#HikariCPServiceConfiguration(Properties, + * System, Properties) + * + * @see #buildFor(Set, Properties) + * + * @see #appliesTo(Properties, System, Properties) + */ + protected HikariCPServiceConfiguration create(final Properties properties, final System system, final Properties coordinates) { + Objects.requireNonNull(properties); + return new HikariCPServiceConfiguration(properties, system, coordinates); + } + + /** + * Overrides the {@link ServiceConfigurationProvider#buildFor(Set, + * Properties)} method to ensure that there is an {@linkplain + * ServiceConfigurationProvider#getAuthoritativeSystem(Set, + * Properties) authoritative System} and then, if so, + * calls the {@link #appliesTo(Properties, System, Properties)} + * method, and, if that returns {@code true}, then calls the {@link + * #create(Properties, System, Properties)} method and returns its + * result. + * + *

This method may—and often does—return {@code + * null}.

+ * + * @param systems a {@link Set} of {@link System}s that will help + * determine whether a {@link ServiceConfiguration} is in effect or + * not; may be {@code null} + * + * @param coordinates a {@link Properties} instance representing the + * meta-properties in effect; may be {@code null} + * + * @return a new {@link ServiceConfiguration} instance suitable for + * the configuration space implied by the supplied {@link Set} of + * {@link System}s and coordinates, or {@code null} + * + * @see #appliesTo(Properties, System, Properties) + * + * @see #create(Properties, System, Properties) + */ + @Override + public final ServiceConfiguration buildFor(final Set systems, final Properties coordinates) { + ServiceConfiguration returnValue = null; + final System system = getAuthoritativeSystem(systems, coordinates); + if (system != null && system.isEnabled()) { + final Properties properties = new Properties(); + if (this.appliesTo(properties, system, coordinates)) { + returnValue = this.create(properties, system, coordinates); + } + } + return returnValue; + } + + /** + * Returns {@code true} if this {@link + * HikariCPServiceConfigurationProvider} is relevant in the + * configuration space described by the supplied properties, {@link + * System} and coordinates. + * + *

The default implementation of this method:

+ * + *
    + * + *
  1. Looks for a property named {@code hikaricp.dataSourceNames}. + * The value of this property is a comma-separated {@link String} + * whose components are names of data sources that will ultimately + * have Hikari connection pools set up for them.
  2. + * + *
  3. If that property is not found, then all property names found + * in the supplied {@code properties}, {@code system} and {@code + * coordinates} are scanned for those starting with {@code + * javax.sql.DataSource.}, and for all names in that subset, the + * next period-separated component of the name is taken to be a data + * source name. For example, a property named {@code + * javax.sql.DataSource.test.dataSourceClassName} will yield a data + * source name of {@code test}.
  4. + * + *
  5. For each such data source name discovered, the {@link + * #installDataSourceProperties(Properties, System, Properties, + * String)} method is called with the parameters supplied to this + * method and the data source name.
  6. + * + *
  7. After this installation step, a property named according to + * the following pattern is sought: {@code + * javax.sql.DataSource.}{@code dataSourceName}{@code + * .dataSourceClassName}.
  8. + * + *
  9. If that yields a class name and the corresponding class can + * be {@linkplain Class#forName(String) loaded}, then {@code true} + * is returned.
  10. + * + *
  11. If that does not yield a class name, then a property named + * according to the following pattern is sought: {@code + * javax.sql.DataSource.}{@code dataSourceName}{@code + * .jdbcUrl}.
  12. + * + *
  13. If that yields a non-{@code null} {@link String} then it is + * passed to the {@link DriverManager#getDriver(String)} method. If + * that method invocation results in a non-{@code null} return + * value, then {@code true} is returned.
  14. + * + *
  15. In all other cases, {@code false} is returned.
  16. + * + *
+ * + * @param properties a {@link Properties} instance that may be used + * later by the {@link #create(Properties, System, Properties)} + * method as the basis of a {@link HikariCPServiceConfiguration} + * implementation; must not be {@code null} + * + * @param system a {@link System} determined to be in effect; may, + * strictly speaking, be {@code null} but ordinarily is non-{@code + * null} and {@linkplain System#isEnabled() enabled} + * + * @param coordinates a {@link Properties} instance representing the + * meta-properties in effect; may be {@code null} + * + * @return {@code true} if this {@link + * HikariCPServiceConfigurationProvider} applies to the + * configuration space implied by the supplied parameters; {@code + * false} otherwise + * + * @exception NullPointerException if {@code properties} is {@code + * null} + */ + protected boolean appliesTo(final Properties properties, final System system, final Properties coordinates) { + Objects.requireNonNull(properties); + boolean returnValue = false; + if (system != null && system.isEnabled()) { + final Collection dataSourceNames = this.getDataSourceNames(properties, system, coordinates); + if (dataSourceNames != null && !dataSourceNames.isEmpty()) { + final String prefix = this.getPrefix(); + assert prefix != null; + for (final String dataSourceName : dataSourceNames) { + installDataSourceProperties(properties, system, coordinates, dataSourceName); + final String dataSourceClassName = + this.getDataSourceProperty(properties, system, coordinates, dataSourceName, "dataSourceClassName"); + if (dataSourceClassName == null) { + final String jdbcUrl = this.getDataSourceProperty(properties, system, coordinates, dataSourceName, "jdbcUrl"); + if (jdbcUrl != null) { + try { + final Object driver = DriverManager.getDriver(jdbcUrl); + assert driver != null; + returnValue = true; + } catch (final SQLException ohWell) { + assert !returnValue; + } + } + } else { + try { + Class.forName(dataSourceClassName); + returnValue = true; + } catch (final ClassNotFoundException classNotFoundException) { + assert !returnValue; + } + } + if (!returnValue) { + break; + } + } + } + } + return returnValue; + } + + private Set getDataSourceNames(final Properties target, final System system, final Properties coordinates) { + Objects.requireNonNull(target); + + Set returnValue = new HashSet<>(); + + final String dataSourceNamesProperty = + getProperty(target, system, coordinates, this.getServiceIdentifier() + ".dataSourceNames", null); + if (dataSourceNamesProperty == null || dataSourceNamesProperty.trim().isEmpty()) { + + final Set allPropertyNames = getPropertyNames(target, system, coordinates); + assert allPropertyNames != null; + + final String prefixWithDot = new StringBuilder(this.getPrefix()).append(".").toString(); + final int prefixWithDotLength = prefixWithDot.length(); + + for (String propertyName : allPropertyNames) { + if (propertyName != null + && propertyName.length() > prefixWithDotLength + && propertyName.startsWith(prefixWithDot)) { + propertyName = propertyName.substring(prefixWithDotLength); + final int dotIndex = propertyName.indexOf('.'); + if (dotIndex > 0) { + returnValue.add(propertyName.substring(0, dotIndex)); + } + } + } + + if (returnValue.isEmpty()) { + returnValue.add(null); + } + } else { + returnValue.addAll(Arrays.asList(WHITESPACE_COMMA_PATTERN.split(dataSourceNamesProperty))); + } + + return returnValue; + } + + /** + * Installs any discoverable properties that might exist that + * pertain to the data source identified by the supplied {@code + * dataSourceName} into the supplied {@code target} {@link + * Properties} object, optionally using the supplied {@code system} + * and {@code coordinates} objects in the process. + * + *

The default implementation of this method:

+ * + *
    + * + *
  1. Looks for a property named {@code hikaricp.}{@code + * dataSourceName}{@code .propertiesPath}. If it is found, + * then it will be converted to a {@link Path} via the {@link + * Paths#get(String, String...)} method.
  2. + * + *
  3. If the resulting {@link Path} identifies a readable file, + * then the file is read into a temporary {@link Properties} object + * via the {@link Properties#load(Reader)} method.
  4. + * + *
  5. {@code target} will now contain a property named {@code + * javax.sql.DataSource.}{@code dataSourceName}{@code + * .explicitlyConfigured} with a {@link String} value of {@code + * true}.
  6. + * + *
  7. Every property that the temporary {@link Properties} object + * contains as a result of reading the file will be added to {@code + * target}, prefixed with {@code javax.sql.DataSource.}{@code + * dataSourceName}{@code .}.
  8. + * + *
+ * + *

If the supplied {@code dataSourceName} is {@code null} or + * {@linkplain String#isEmpty() empty}, or if the property being + * read out of the temporary {@link Properties} object already + * starts with {@code javax.sql.DataSource.}, then it is copied into + * {@code target} without any prefix or modification.

+ * + * @param target the {@link Properties} into which data source + * properties will be installed; must not be {@code null} + * + * @param system a {@link System} determined to be in effect; may, + * strictly speaking, be {@code null} but ordinarily is non-{@code + * null} and {@linkplain System#isEnabled() enabled} + * + * @param coordinates a {@link Properties} instance representing the + * meta-properties in effect; may be {@code null} + * + * @param dataSourceName the name of the data source for which + * properties should be installed; may be {@code null} + * + * @exception NullPointerException if {@code target} is {@code null} + */ + protected void installDataSourceProperties(final Properties target, + final System system, + final Properties coordinates, + final String dataSourceName) { + Objects.requireNonNull(target); + + final String hikariPropertiesPathString = + this.getDataSourceProperty(target, + system, + coordinates, + this.getServiceIdentifier(), + dataSourceName, + "propertiesPath"); + + if (hikariPropertiesPathString != null) { + + final Properties temp = new Properties(); + try (Reader reader = Files.newBufferedReader(Paths.get(hikariPropertiesPathString), StandardCharsets.UTF_8)) { + temp.load(reader); + this.setDataSourceProperty(target, dataSourceName, "explicitlyConfigured", "true"); + } catch (final IOException ohWell) { + + } + + if (!temp.isEmpty()) { + + // Now read each property out of temp, prefix its name with + // ., and set it in target. + // + // So test.properties' jdbcUrl=jdbc:foo:bar becomes + // javax.sql.DataSource.test.jdbcUrl=jdbc:foo:bar. + + final Collection names = temp.stringPropertyNames(); + assert names != null; + assert !names.isEmpty(); + + final String prefix = this.getPrefix(); + assert prefix != null; + + for (final String unprefixedPropertyName : names) { + if (unprefixedPropertyName != null && !unprefixedPropertyName.isEmpty()) { + if (dataSourceName == null + || dataSourceName.isEmpty() + || unprefixedPropertyName.startsWith(prefix + "." + dataSourceName + ".")) { + target.setProperty(unprefixedPropertyName, + temp.getProperty(unprefixedPropertyName)); + } else { + target.setProperty(prefix + "." + dataSourceName + "." + unprefixedPropertyName, + temp.getProperty(unprefixedPropertyName)); + } + } + } + + } + + } + } + + private Object setDataSourceProperty(final Properties properties, + String dataSourceName, + final String unprefixedPropertyName, + final String propertyValue) { + Objects.requireNonNull(properties); + final Object returnValue = + properties.setProperty(prefixDataSourcePropertyName(this.getPrefix(), dataSourceName, unprefixedPropertyName), + propertyValue); + return returnValue; + } + + + /** + * Returns the value of a property found in the {@code properties} + * parameter value, or, failing that, in the supplied {@link + * System}'s {@linkplain System#getProperties() properties}, or, + * failing that, in the supplied {@code coordinates} parameter + * value, that applies to the data source identified by the supplied + * {@code dataSourceName} parameter, taking into account the + * {@linkplain #getPrefix() prefix}. + * + *

This method may return {@code null}.

+ * + * @param properties the {@link Properties} to check first; must not + * be {@code null} + * + * @param system a {@link System} determined to be in effect; may, + * strictly speaking, be {@code null} but ordinarily is non-{@code + * null} and {@linkplain System#isEnabled() enabled} + * + * @param coordinates a {@link Properties} instance representing the + * meta-properties in effect; may be {@code null} + * + * @param dataSourceName the name of a data source; may be {@code + * null} + * + * @param unprefixedPropertyName the "simple" property name being + * sought; must not be {@code null} + * + * @return the value of the property, or {@code null} if no such + * property exists + * + * @exception NullPointerException if {@code properties} or {@code + * unprefixedPropertyName} is {@code null} + */ + protected final String getDataSourceProperty(final Properties properties, + final System system, + final Properties coordinates, + String dataSourceName, + final String unprefixedPropertyName) { + return this.getDataSourceProperty(properties, system, coordinates, this.getPrefix(), dataSourceName, unprefixedPropertyName); + } + + private String getDataSourceProperty(final Properties properties, + final System system, + final Properties coordinates, + final String prefix, + String dataSourceName, + final String unprefixedPropertyName) { + final String returnValue = + getProperty(properties, + system, + coordinates, + prefixDataSourcePropertyName(prefix, dataSourceName, unprefixedPropertyName), + null); + return returnValue; + } + + + /* + * Static methods. + */ + + + private static String prefixDataSourcePropertyName(final String prefix, + String dataSourceName, + final String unprefixedPropertyName) { + Objects.requireNonNull(prefix); + if (prefix.isEmpty()) { + throw new IllegalArgumentException("prefix.isEmpty()"); + } + Objects.requireNonNull(unprefixedPropertyName); + + final String prefixedPropertyName; + if (dataSourceName == null) { + prefixedPropertyName = prefix + "." + unprefixedPropertyName; + } else { + dataSourceName = dataSourceName.trim(); + if (dataSourceName.isEmpty()) { + prefixedPropertyName = prefix + "." + unprefixedPropertyName; + } else { + prefixedPropertyName = prefix + "." + dataSourceName + "." + unprefixedPropertyName; + } + } + return prefixedPropertyName; + } + + private static Set getPropertyNames(final Properties properties, + final System system, + final Properties coordinates) { + Objects.requireNonNull(properties); + final Set returnValue = new HashSet<>(); + final Properties systemProperties; + if (system == null || !system.isEnabled()) { + systemProperties = null; + } else { + systemProperties = system.getProperties(); + } + if (systemProperties != null) { + returnValue.addAll(systemProperties.stringPropertyNames()); + } + returnValue.addAll(properties.stringPropertyNames()); + if (coordinates != null) { + returnValue.addAll(coordinates.stringPropertyNames()); + } + return Collections.unmodifiableSet(returnValue); + } + + private static String getProperty(final Properties properties, + final System system, + final Properties coordinates, + final String propertyName, + final String defaultValue) { + Objects.requireNonNull(properties); + Objects.requireNonNull(propertyName); + String returnValue = properties.getProperty(propertyName); + if (returnValue == null) { + if (coordinates != null) { + returnValue = coordinates.getProperty(propertyName); + } + if (returnValue == null) { + if (system == null || !system.isEnabled()) { + returnValue = java.lang.System.getProperty(propertyName, defaultValue); + } else { + final Properties systemProperties = system.getProperties(); + if (systemProperties == null) { + returnValue = defaultValue; + } else { + returnValue = systemProperties.getProperty(propertyName, defaultValue); + } + } + } + } + return returnValue; + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/main/java/io/helidon/service/configuration/hikaricp/package-info.java b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/main/java/io/helidon/service/configuration/hikaricp/package-info.java new file mode 100644 index 00000000000..e6c9899cdba --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/main/java/io/helidon/service/configuration/hikaricp/package-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides classes and interfaces for automatically discovering + * service configuration information relevant to Hikari + * connection pool componentry. + * + * @author Laird Nelson + * + * @see io.helidon.service.configuration.hikaricp.HikariCPServiceConfiguration + */ +package io.helidon.service.configuration.hikaricp; diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/main/javadoc/overview.html b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/main/javadoc/overview.html new file mode 100644 index 00000000000..7c9c21b4d76 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/main/javadoc/overview.html @@ -0,0 +1,27 @@ + + +

Provides classes and interfaces for auto-discovering + configuration for the Hikari connection pool.

+ + @author Laird Nelson + + @see io.helidon.service.configuration.hikaricp.HikariCPServiceConfiguration + + @see io.helidon.service.configuration.hikaricp.HikariCPServiceConfigurationProvider + diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/test/java/io/helidon/service/configuration/hikaricp/TestPropertiesScenarios.java b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/test/java/io/helidon/service/configuration/hikaricp/TestPropertiesScenarios.java new file mode 100644 index 00000000000..333f31060b3 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/test/java/io/helidon/service/configuration/hikaricp/TestPropertiesScenarios.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.hikaricp; + +import java.util.Properties; +import java.util.Set; + +import io.helidon.service.configuration.api.System; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class TestPropertiesScenarios { + + public TestPropertiesScenarios() { + super(); + } + + @Test + public void testLocalhostIsPresent() { + final Set systems = System.getSystems(); + assertNotNull(systems); + assertEquals(1, systems.size()); + final System system = systems.iterator().next(); + assertNotNull(system); + assertEquals("localhost", system.getName()); + } + + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/test/resources/TestPropertiesScenarios/test.properties b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/test/resources/TestPropertiesScenarios/test.properties new file mode 100644 index 00000000000..bbbe954c999 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-hikaricp/src/test/resources/TestPropertiesScenarios/test.properties @@ -0,0 +1,14 @@ +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +jdbcUrl=jdbc:h2:mem:test diff --git a/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/README.adoc b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/README.adoc new file mode 100644 index 00000000000..6adcb8ebd74 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/README.adoc @@ -0,0 +1,10 @@ += Kubernetes System Implementation + +This project provides a `System` implementation for Kubernetes +platforms. + +== Installation + +Ensure the `serviceconfiguration-system-kubernetes` artifact is on +your runtime classpath. + diff --git a/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/pom.xml b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/pom.xml new file mode 100644 index 00000000000..8fa3762318b --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/pom.xml @@ -0,0 +1,51 @@ + + + + 4.0.0 + + helidon-serviceconfiguration-system-kubernetes + + Helidon Kubernetes System Implementation + ${project.name} + + + io.helidon.serviceconfiguration + helidon-serviceconfiguration-project + 0.10.3-SNAPSHOT + + + + + org.junit.jupiter + junit-jupiter-api + test + + + + ${project.groupId} + helidon-serviceconfiguration-api + compile + + + + + diff --git a/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/main/java/io/helidon/service/configuration/kubernetes/KubernetesSystem.java b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/main/java/io/helidon/service/configuration/kubernetes/KubernetesSystem.java new file mode 100644 index 00000000000..7f0055224a8 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/main/java/io/helidon/service/configuration/kubernetes/KubernetesSystem.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.kubernetes; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +import io.helidon.service.configuration.api.System; + +/** + * A non-{@linkplain System#isAuthoritative() authoritative} {@link + * System} implementation that {@linkplain #isEnabled() is enabled} + * when running on any of several possible Kubernetes systems. + * + * @author Laird Nelson + * + * @see #isEnabled() + * + * @see Kubernetes + */ +public final class KubernetesSystem extends System { + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link KubernetesSystem} whose {@linkplain + * #getName() name} is {@code kubernetes} and whose {@linkplain + * #isAuthoritative() authoritative status} is {@code false}. + * + * @see System#System(String, boolean) + */ + public KubernetesSystem() { + super("kubernetes", false /* not authoritative; don't know if it's minikube, GKE, AKS, etc. */); + } + + + /* + * Instance methods. + */ + + + /** + * Returns {@code true} if there is a file named {@code + * /proc/1/cpuset} that contains at least one line starting with + * {@code /kubepods/}. + * + * @return {@code true} if the caller is running on any of several + * possible Kubernetes systems; + * {@code false} otherwise + * + * @see System#isEnabled() + * + * @see Kubernetes + */ + @Override + public boolean isEnabled() { + try { + return + Files.lines(Paths.get("/proc/1/cpuset"), StandardCharsets.UTF_8) + .filter(l -> l.startsWith("/kubepods/")) + .findAny() + .isPresent(); + } catch (final IOException ioException) { + return false; + } + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/main/java/io/helidon/service/configuration/kubernetes/package-info.java b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/main/java/io/helidon/service/configuration/kubernetes/package-info.java new file mode 100644 index 00000000000..84c92e3bcea --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/main/java/io/helidon/service/configuration/kubernetes/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides classes and interfaces related to service configuration on + * a Kubernetes system. + * + * @author Laird Nelson + * + * @see io.helidon.service.configuration.kubernetes.KubernetesSystem + */ +package io.helidon.service.configuration.kubernetes; diff --git a/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/main/javadoc/overview.html b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/main/javadoc/overview.html new file mode 100644 index 00000000000..8006d3fad87 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/main/javadoc/overview.html @@ -0,0 +1,28 @@ + + +

Provides a {@link io.helidon.service.configuration.api.System} + implementation for determining when programs are running + in Kubernetes.

+ + @author Laird Nelson + + @see io.helidon.service.configuration.kubernetes.KubernetesSystem + + @see io.helidon.service.configuration.api.System + diff --git a/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/main/resources/META-INF/services/io.helidon.service.configuration.api.System b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/main/resources/META-INF/services/io.helidon.service.configuration.api.System new file mode 100644 index 00000000000..99515cabb9c --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/main/resources/META-INF/services/io.helidon.service.configuration.api.System @@ -0,0 +1,14 @@ +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +io.helidon.service.configuration.kubernetes.KubernetesSystem \ No newline at end of file diff --git a/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/test/java/io/helidon/service/configuration/kubernetes/TestKubernetesSystem.java b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/test/java/io/helidon/service/configuration/kubernetes/TestKubernetesSystem.java new file mode 100644 index 00000000000..30d7b7a8d05 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-system-kubernetes/src/test/java/io/helidon/service/configuration/kubernetes/TestKubernetesSystem.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.kubernetes; + +import java.io.IOException; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestKubernetesSystem { + + public TestKubernetesSystem() { + super(); + } + + @Test + public void testEnabled() throws IOException { + final KubernetesSystem system = new KubernetesSystem(); + final Path proc1CpuSet = Paths.get("/proc/1/cpuset"); + + if (Files.exists(proc1CpuSet)) { + final String singleLine = Files.lines(proc1CpuSet).findAny().get(); + assertNotNull(singleLine); + if (singleLine.startsWith("/kubepods/")) { + assertTrue(system.isEnabled()); + } else { + assertFalse(system.isEnabled()); + } + } else { + assertFalse(system.isEnabled()); + } + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/README.adoc b/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/README.adoc new file mode 100644 index 00000000000..a0ab14dd2d5 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/README.adoc @@ -0,0 +1,10 @@ += Oracle ACCS System Implementation + +This project provides a `System` implementation for Oracle's +https://cloud.oracle.com/acc[Application Container Cloud Service]. + +== Installation + +Ensure the `serviceconfiguration-system-oracle-accs` artifact is on +your runtime classpath. + diff --git a/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/pom.xml b/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/pom.xml new file mode 100644 index 00000000000..679db6ab22b --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/pom.xml @@ -0,0 +1,45 @@ + + + + 4.0.0 + + helidon-serviceconfiguration-system-oracle-accs + + Helidon Oracle ACCS System Implementation + ${project.name} + + + io.helidon.serviceconfiguration + helidon-serviceconfiguration-project + 0.10.3-SNAPSHOT + + + + + ${project.groupId} + helidon-serviceconfiguration-api + compile + + + + + diff --git a/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/src/main/java/io/helidon/service/configuration/accs/ACCSSystem.java b/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/src/main/java/io/helidon/service/configuration/accs/ACCSSystem.java new file mode 100644 index 00000000000..6e4fa8c3d28 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/src/main/java/io/helidon/service/configuration/accs/ACCSSystem.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.service.configuration.accs; + +import java.util.Map; + +import io.helidon.service.configuration.api.System; + +/** + * A {@link System} implementation that represents the Oracle + * Application Container Cloud Service system. + * + *

This {@link System} is {@linkplain #isEnabled() enabled} when + * {@linkplain #getenv() its environment} {@linkplain + * Map#containsKey(Object) contains the String key} + * {@code ORA_APP_NAME}.

+ * + * @author Laird Nelson + * + * @see #isEnabled() + * + * @see System + */ +public final class ACCSSystem extends System { + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link ACCSSystem} whose {@link #getName() name} is + * {@code accs} and whose {@linkplain #isAuthoritative() + * authoritative status} is {@code true}. + * + * @see #isAuthoritative() + */ + public ACCSSystem() { + super("accs", true); + } + + + /* + * Instance methods. + */ + + + /** + * Returns {@code true} if this {@link ACCSSystem}'s {@linkplain + * #getenv() environment} {@linkplain Map#containsKey(Object) + * contains the String key} {@code ORA_APP_NAME}. + * + * @return {@code true} if this {@link ACCSSystem} is enabled; + * {@code false} otherwise + * + * @see Configuring + * Environment Variables in the Oracle Application Container Cloud + * Service documentation + * + * @see System#isEnabled() + */ + @Override + public boolean isEnabled() { + final Map env = this.getenv(); + return env != null && env.containsKey("ORA_APP_NAME"); + } + +} diff --git a/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/src/main/java/io/helidon/service/configuration/accs/package-info.java b/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/src/main/java/io/helidon/service/configuration/accs/package-info.java new file mode 100644 index 00000000000..956267e7832 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/src/main/java/io/helidon/service/configuration/accs/package-info.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides the {@link + * io.helidon.service.configuration.accs.ACCSSystem} {@link + * io.helidon.service.configuration.api.System System} implementation. + * + * @author Laird Nelson + * + * @see io.helidon.service.configuration.accs.ACCSSystem + * + * @see io.helidon.service.configuration.api.System + */ +package io.helidon.service.configuration.accs; diff --git a/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/src/main/javadoc/overview.html b/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/src/main/javadoc/overview.html new file mode 100644 index 00000000000..920c7714831 --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/src/main/javadoc/overview.html @@ -0,0 +1,27 @@ + + +

Provides classes and interfaces representing + the Oracle + Application Container Cloud Service using the concepts and + constructs defined by the {@linkplain + io.helidon.service.configuration.api.System Service Configuration + API}.

+ + @author Laird Nelson + diff --git a/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/src/main/resources/META-INF/services/io.helidon.service.configuration.api.System b/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/src/main/resources/META-INF/services/io.helidon.service.configuration.api.System new file mode 100644 index 00000000000..9454075134a --- /dev/null +++ b/integrations/serviceconfiguration/serviceconfiguration-system-oracle-accs/src/main/resources/META-INF/services/io.helidon.service.configuration.api.System @@ -0,0 +1,14 @@ +# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +io.helidon.service.configuration.accs.ACCSSystem \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6de02747865..369e0258be3 100644 --- a/pom.xml +++ b/pom.xml @@ -115,11 +115,14 @@ 2.4.14 1.11.0 25.0-jre + 1.4.197 1.3 + 2.7.8 2.9.4 2.0.4.Final 2.3.0 2.1 + 2.9.0 2.26 4.9.0.201710071750-r 1.19 @@ -133,7 +136,10 @@ 1.1 1.0 2.15.0 + 8.0.11 4.1.22.Final + 1.2.44 + 12.2.0.1 0.31.0 0.4.0 3.5.1 @@ -175,9 +181,11 @@ 1.16 1.6.7 1.5.0.Final + 3.0.0 0.5.0 2.7 3.0.0 + 3.7.1 1.0.0 3.0.1 3.1.3.1 @@ -197,6 +205,7 @@ security bom microprofile + integrations @@ -220,16 +229,26 @@ ${version.plugin.javadoc} all - - -source - 8 + + -J-Dhttp.agent=maven-javadoc-plugin + 8 **/module-info.java target/**/*.java + + org.apache.maven.plugins + maven-project-info-reports-plugin + ${version.plugin.project-info-reports} + + + org.apache.maven.plugins + maven-site-plugin + ${version.plugin.site} + org.apache.maven.plugins maven-deploy-plugin @@ -876,6 +895,42 @@
+ + + com.h2database + h2 + ${version.lib.h2} + + + + com.oracle.jdbc + ojdbc8 + ${version.lib.ojdbc8} + + + mysql + mysql-connector-java + ${version.lib.mysql-connector-java} + + + com.oracle.oci.sdk + oci-java-sdk-objectstorage + ${version.lib.oci-java-sdk-objectstorage} + pom + + + com.zaxxer + HikariCP + ${version.lib.HikariCP} + + + redis.clients + jedis + ${version.lib.jedis} + + org.mockito