diff --git a/.gitignore b/.gitignore
index bb39c50bea0..000016a72ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,3 +67,5 @@ node/
# Other
*~
user.txt
+ObjectStore/
+PutObjectStoreDirHere/
diff --git a/3RD-PARTY.txt b/3RD-PARTY.txt
index 612310294b5..b76e960ba78 100644
--- a/3RD-PARTY.txt
+++ b/3RD-PARTY.txt
@@ -2220,3 +2220,23 @@ from the source code management (SCM) system project uses.
-----------------jackson-annotations 2.9.8 -----------------------
COPYRIGHT: Copyright (c) 2007- Tatu Saloranta, tatu.saloranta@iki.fi
LICENSE: Apache 2.0
+
+=======================
+SnakeYAML 1.24
+=======================
+Copyright (c) 2008, http://www.snakeyaml.org
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+LICENSE: Apache 2.0
+
diff --git a/bom/pom.xml b/bom/pom.xml
index 644ebdcd439..9160ed57a60 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -52,6 +52,17 @@
helidon-webserver-test-support${project.version}
+
+
+ io.helidon.grpc
+ helidon-grpc-client
+ ${project.version}
+
+
+ io.helidon.grpc
+ helidon-grpc-server
+ ${project.version}
+ io.helidon.media
@@ -170,6 +181,11 @@
helidon-security-providers-http-sign${project.version}
+
+ io.helidon.security.integration
+ helidon-security-integration-grpc
+ ${project.version}
+ io.helidon.security.integrationhelidon-security-integration-jersey
@@ -186,10 +202,10 @@
${project.version}
- io.helidon.security.providers
- helidon-security-providers-abac
- ${project.version}
-
+ io.helidon.security.providers
+ helidon-security-providers-abac
+ ${project.version}
+
io.helidon.securityhelidon-security-abac-time
@@ -318,6 +334,11 @@
helidon-common-key-util${project.version}
+
+ io.helidon.common
+ helidon-common-service-loader
+ ${project.version}
+
@@ -398,11 +419,36 @@
helidon-integrations-cdi-datasource-hikaricp${project.version}
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-eclipselink
+ ${project.version}
+ io.helidon.integrations.cdihelidon-integrations-cdi-jedis${project.version}
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-jpa
+ ${project.version}
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-jpa-weld
+ ${project.version}
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-jta
+ ${project.version}
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-jta-weld
+ ${project.version}
+ io.helidon.integrations.cdihelidon-integrations-cdi-oci-objectstorage
diff --git a/common/common/src/main/java/io/helidon/common/Prioritized.java b/common/common/src/main/java/io/helidon/common/Prioritized.java
new file mode 100644
index 00000000000..5d6ef845dce
--- /dev/null
+++ b/common/common/src/main/java/io/helidon/common/Prioritized.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.common;
+
+/**
+ * Interface to define that this class is a class with priority.
+ * One of the uses is for services loaded by a ServiceLoader.
+ *
+ * A {@code Prioritized} with lower priority number is more significant than a {@code Prioritized} with a
+ * higher priority number.
+ *
+ * For cases where priority is the same, implementation must define ordering of such {@code Prioritized}.
+ *
+ * Negative priorities are not allowed and services using priorities should throw an
+ * {@link java.lang.IllegalArgumentException} if such a priority is used (unless such a service
+ * documents the specific usage of a negative priority)
+ *
+ * A {@code Prioritized} with priority {@code 1} is more significant (will be returned before) priority {@code 2}.
+ */
+@FunctionalInterface
+public interface Prioritized {
+ /**
+ * Default priority for any prioritized component (whether it implements this interface
+ * or uses {@code javax.annotation.Priority} annotation.
+ */
+ int DEFAULT_PRIORITY = 5000;
+
+ /**
+ * Priority of this class (maybe because it is defined
+ * dynamically, so it cannot be defined by an annotation).
+ * If not dynamic, you can use the {@code javax.annotation.Priority}
+ * annotation rather then implementing this interface as long as
+ * it is supported by the library using this {@code Prioritized}.
+ *
+ * @return the priority of this service, must be a non-negative number
+ */
+ int priority();
+}
diff --git a/common/common/src/main/templates/io/helidon/common/Version.java b/common/common/src/main/templates/io/helidon/common/Version.java
index bac7bfa39b4..55a9987a2d3 100644
--- a/common/common/src/main/templates/io/helidon/common/Version.java
+++ b/common/common/src/main/templates/io/helidon/common/Version.java
@@ -26,12 +26,17 @@ public class Version {
*/
public static final String VERSION = "${project.version}";
+ /**
+ * Revision Number.
+ */
+ public static final String REVISION = "${buildNumber}";
+
/**
* Display version
*
- * @param args
+ * @param args Ignored
*/
public static void main(String[] args) {
- System.out.println(VERSION);
+ System.out.println(VERSION + " " + REVISION);
}
}
diff --git a/common/configurable/src/main/java/io/helidon/common/configurable/ResourceUtil.java b/common/configurable/src/main/java/io/helidon/common/configurable/ResourceUtil.java
index c2559ce77a3..d63f85f7576 100644
--- a/common/configurable/src/main/java/io/helidon/common/configurable/ResourceUtil.java
+++ b/common/configurable/src/main/java/io/helidon/common/configurable/ResourceUtil.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -90,7 +90,7 @@ static InputStream toIs(URI uri) {
try {
return uri.toURL().openStream();
} catch (IOException e) {
- throw new ResourceException("Failed to open strem to uri: " + uri, e);
+ throw new ResourceException("Failed to open stream to uri: " + uri, e);
}
}
@@ -105,7 +105,7 @@ static InputStream toIs(URI uri, Proxy proxy) {
try {
return uri.toURL().openConnection(proxy).getInputStream();
} catch (IOException e) {
- throw new ResourceException("Failed to open strem to uri: " + uri, e);
+ throw new ResourceException("Failed to open stream to uri: " + uri, e);
}
}
diff --git a/common/pom.xml b/common/pom.xml
index ac982542ef5..fc67643272f 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -36,5 +36,6 @@
configurablekey-utilhttp
+ service-loader
diff --git a/common/service-loader/pom.xml b/common/service-loader/pom.xml
new file mode 100644
index 00000000000..63e64013945
--- /dev/null
+++ b/common/service-loader/pom.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+ helidon-common-project
+ io.helidon.common
+ 1.0.4-SNAPSHOT
+
+ 4.0.0
+
+ helidon-common-service-loader
+ Helidon Common Service Loader
+
+
+ Service loader utilities to extend functionality of
+ Java Service loader.
+
+
+
+
+ io.helidon.common
+ helidon-common
+ ${project.version}
+
+
+ javax.annotation
+ javax.annotation-api
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
\ No newline at end of file
diff --git a/common/service-loader/src/main/java/io/helidon/common/serviceloader/HelidonServiceLoader.java b/common/service-loader/src/main/java/io/helidon/common/serviceloader/HelidonServiceLoader.java
new file mode 100644
index 00000000000..8ffea1d4c51
--- /dev/null
+++ b/common/service-loader/src/main/java/io/helidon/common/serviceloader/HelidonServiceLoader.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.common.serviceloader;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import javax.annotation.Priority;
+
+import io.helidon.common.Prioritized;
+
+/**
+ * Helidon specific support for Java Service Loaders.
+ *
+ * This service loader:
+ *
+ *
Can have additional implementations added
+ *
Uses priorities defined either by {@link io.helidon.common.Prioritized}
+ * or by {@link javax.annotation.Priority}
+ *
Can have exclusions defined by an exact implementation class name, either
+ * in {@link Builder#addExcludedClass(Class)} or {@link Builder#addExcludedClassName(String)} or
+ * by a system property {@value #SYSTEM_PROPERTY_EXCLUDE} that defines
+ * a comma separated list of fully qualified class names to be excluded.
+ * Note that if a class implements more than one service, it would be excluded from all.
+ *
+ *
+ * Note on priority handling
+ *
+ * Service priority is defined by:
+ *
+ *
Value provided in {@link Builder#addService(Object, int)} (if used)
+ *
then by {@link io.helidon.common.Prioritized#priority()} if service implements it
+ *
then by {@link javax.annotation.Priority} annotation if present
+ *
otherwise a default priority {@value Prioritized#DEFAULT_PRIORITY} from {@link Prioritized#DEFAULT_PRIORITY} is used
+ *
+ * Example:
+ *
+ * {@literal @}Priority(4500)
+ * public class MyServiceImpl implements Service, Prioritized {
+ * public int priority() {
+ * return 6200;
+ * }
+ * }
+ *
+ * Such a service would have a priority of {@code 6200} as that is more significant than the annotation.
+ *
+ * A service with lower priority number is returned before a service with a higher priority number.
+ * Services with the same priority have order defined by the order they are in the configured services
+ * and then as they are loaded from the {@link java.util.ServiceLoader}.
+ * Negative priorities are not allowed.
+ * A service with priority {@code 1} will be returned before a service with priority {@code 2}.
+ *
+ * @param Type of the service to be loaded
+ * @see java.util.ServiceLoader
+ * @see #builder(java.util.ServiceLoader)
+ */
+public final class HelidonServiceLoader implements Iterable {
+ /**
+ * System property used to exclude some implementation from the list of services that are configured for Java Service
+ * loader or services that are registered using {@link io.helidon.common.serviceloader.HelidonServiceLoader.Builder}.
+ */
+ public static final String SYSTEM_PROPERTY_EXCLUDE = "io.helidon.common.serviceloader.exclude";
+
+ private static final Logger LOGGER = Logger.getLogger(HelidonServiceLoader.class.getName());
+
+ private final List services;
+
+ /**
+ * Create a builder for customizable service loader.
+ *
+ * @param serviceLoader the Java Service loader used to get service implementations
+ * @param type of the service
+ * @return a new fluent API builder
+ */
+ public static Builder builder(ServiceLoader serviceLoader) {
+ return new Builder<>(serviceLoader);
+ }
+
+ /**
+ * Create a prioritized service loader from a Java Service loader.
+ *
+ * @param serviceLoader the Java service loader
+ * @param type of the service
+ * @return service loader with exclusions defined by system properties and no custom services
+ */
+ public static HelidonServiceLoader create(ServiceLoader serviceLoader) {
+ Builder builder = builder(serviceLoader);
+ return builder.build();
+ }
+
+ private HelidonServiceLoader(List services) {
+ this.services = new LinkedList<>(services);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return Collections.unmodifiableList(services)
+ .iterator();
+ }
+
+ @Override
+ public void forEach(Consumer super T> action) {
+ this.services.forEach(action);
+ }
+
+ /**
+ * Provides a list of service implementations in prioritized order.
+ *
+ * @return list of service implementations
+ */
+ public List asList() {
+ return new LinkedList<>(this.services);
+ }
+
+ /**
+ * Fluent api builder for {@link io.helidon.common.serviceloader.HelidonServiceLoader}.
+ *
+ * @param type of the service to be loaded
+ */
+ public static final class Builder implements io.helidon.common.Builder> {
+ private final ServiceLoader serviceLoader;
+ private final List> customServices = new LinkedList>();
+ private final Set excludedServiceClasses = new HashSet<>();
+ private boolean useSysPropExclude = true;
+ private boolean useSystemServiceLoader = true;
+ private boolean replaceImplementations = true;
+
+ private Builder(ServiceLoader serviceLoader) {
+ this.serviceLoader = serviceLoader;
+ }
+
+ @Override
+ public HelidonServiceLoader build() {
+ // first merge the lists together
+ List> services = new LinkedList<>(customServices);
+ if (useSystemServiceLoader) {
+ Set uniqueImplementations = new HashSet<>();
+
+ if (replaceImplementations) {
+ customServices.stream()
+ .map(ServiceWithPriority::instanceClassName)
+ .forEach(uniqueImplementations::add);
+ }
+
+ serviceLoader.forEach(service -> {
+ if (replaceImplementations) {
+ if (!uniqueImplementations.contains(service.getClass().getName())) {
+ services.add(new ServiceWithPriority<>(service));
+ }
+ } else {
+ services.add(new ServiceWithPriority<>(service));
+ }
+ });
+ }
+
+ if (useSysPropExclude) {
+ addSystemExcludes();
+ }
+ List> withoutExclusions = services.stream()
+ .filter(this::notExcluded)
+ .collect(Collectors.toList());
+
+ // order by priority
+ return new HelidonServiceLoader<>(orderByPriority(withoutExclusions));
+ }
+
+ /**
+ * When configured to use system excludes, system property {@value #SYSTEM_PROPERTY_EXCLUDE} is used to get the
+ * comma separated list of service implementations to exclude them from the loaded list.
+ *
+ * This defaults to {@code true}.
+ *
+ * @param useSysPropExclude whether to use a system property to exclude service implementations
+ * @return updated builder instance
+ */
+ public Builder useSystemExcludes(boolean useSysPropExclude) {
+ this.useSysPropExclude = useSysPropExclude;
+ return this;
+ }
+
+ /**
+ * When configured to use Java Service loader, then the result is a combination of all service implementations
+ * loaded from the Java Service loader and those added by {@link #addService(Object)} or {@link #addService(Object, int)}.
+ * When set to {@code false} the Java Service loader is ignored.
+ *
+ * This defaults to {@code true}.
+ *
+ * @param useServiceLoader whether to use the Java Service loader
+ * @return updated builder instance
+ */
+ public Builder useSystemServiceLoader(boolean useServiceLoader) {
+ this.useSystemServiceLoader = useServiceLoader;
+ return this;
+ }
+
+ /**
+ * When configured to replace implementations, then a service implementation configured through
+ * {@link #addService(Object)}
+ * will replace the same implementation loaded from the Java Service loader (compared by fully qualified class name).
+ *
+ * This defaults to {@code true}.
+ *
+ * @param replace whether to replace service instances loaded by java service loader with the ones provided
+ * through builder methods
+ * @return updated builder instance
+ */
+ public Builder replaceImplementations(boolean replace) {
+ this.replaceImplementations = replace;
+ return this;
+ }
+
+ /**
+ * Add a custom service implementation to the list of services.
+ *
+ * @param service a new service instance
+ * @return updated builder instance
+ */
+ public Builder addService(T service) {
+ this.customServices.add(new ServiceWithPriority<>(service));
+ return this;
+ }
+
+ /**
+ * Add a custom service implementation to the list of services with a custom priority.
+ *
+ * @param service a new service instance
+ * @param priority priority to use when ordering service instances
+ * @return updated builder instance
+ */
+ public Builder addService(T service, int priority) {
+ this.customServices.add(new ServiceWithPriority<>(service, priority));
+ return this;
+ }
+
+ /**
+ * Add an excluded implementation class - if such a service implementation is configured (either through
+ * Java Service loader or through {@link #addService(Object)}), it would be ignored.
+ *
+ * @param excluded excluded implementation class
+ * @return updated builder instance
+ */
+ public Builder addExcludedClass(Class extends T> excluded) {
+ excludedServiceClasses.add(excluded.getName());
+ return this;
+ }
+
+ /**
+ * Add an excluded implementation class - if such a service implementation is configured (either through
+ * Java Service loader or through {@link #addService(Object)}), it would be ignored.
+ *
+ * @param excludeName excluded implementation class name
+ * @return updated builder instance
+ */
+ public Builder addExcludedClassName(String excludeName) {
+ excludedServiceClasses.add(excludeName);
+ return this;
+ }
+
+ private boolean notExcluded(ServiceWithPriority service) {
+ String className = service.instance.getClass().getName();
+ if (excludedServiceClasses.contains(className)) {
+ LOGGER.finest(() -> "Excluding service implementation " + className);
+ return false;
+ }
+ return true;
+ }
+
+ private List orderByPriority(List> services) {
+ services.sort(ServiceWithPriority.COMPARATOR);
+
+ List result = services.stream()
+ .map(ServiceWithPriority::instance)
+ .collect(Collectors.toList());
+
+ if (LOGGER.isLoggable(Level.FINEST)) {
+ LOGGER.finest("Final order of enabled service implementations for service: " + serviceLoader);
+ result.stream()
+ .map(Object::getClass)
+ .map(Class::getName)
+ .forEach(LOGGER::finest);
+ }
+
+ return result;
+ }
+
+ private void addSystemExcludes() {
+ String excludes = System.getProperty(SYSTEM_PROPERTY_EXCLUDE);
+ if (null == excludes) {
+ return;
+ }
+
+ for (String exclude : excludes.split(",")) {
+ LOGGER.finest(() -> "Adding exclude from system properties: " + exclude);
+ addExcludedClassName(exclude);
+ }
+ }
+
+ private static final class ServiceWithPriority {
+ public static final Comparator> COMPARATOR = Comparator
+ .comparingInt(ServiceWithPriority::priority);
+
+ private final T instance;
+ private final int priority;
+
+ private ServiceWithPriority(T instance, int priority) {
+ this.instance = instance;
+ this.priority = priority;
+
+ if (priority < 0) {
+ throw new IllegalArgumentException("Service: "
+ + instance.getClass().getName()
+ + " declares a negative priority, which is not allowed. Priority: "
+ + priority);
+ }
+ }
+
+ private ServiceWithPriority(T service) {
+ this(service, findPriority(service));
+ }
+
+ private int priority() {
+ return priority;
+ }
+
+ private T instance() {
+ return instance;
+ }
+
+ private String instanceClassName() {
+ return instance.getClass().getName();
+ }
+
+ private static int findPriority(Object o) {
+ if (o instanceof Prioritized) {
+ return ((Prioritized) o).priority();
+ }
+ Priority prio = o.getClass().getAnnotation(Priority.class);
+ if (null == prio) {
+ return Prioritized.DEFAULT_PRIORITY;
+ }
+ return prio.value();
+ }
+
+ @Override
+ public String toString() {
+ return instance.toString();
+ }
+ }
+ }
+}
diff --git a/common/service-loader/src/main/java/io/helidon/common/serviceloader/package-info.java b/common/service-loader/src/main/java/io/helidon/common/serviceloader/package-info.java
new file mode 100644
index 00000000000..8e7027c71aa
--- /dev/null
+++ b/common/service-loader/src/main/java/io/helidon/common/serviceloader/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Java Service loader extension.
+ */
+package io.helidon.common.serviceloader;
diff --git a/common/service-loader/src/main/java9/module-info.java b/common/service-loader/src/main/java9/module-info.java
new file mode 100644
index 00000000000..37fb8a0feca
--- /dev/null
+++ b/common/service-loader/src/main/java9/module-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Helidon Common Service Loader.
+ */
+module io.helidon.common.serviceloader {
+ requires java.logging;
+ requires io.helidon.common;
+ requires java.annotation;
+
+ exports io.helidon.common.serviceloader;
+}
diff --git a/common/service-loader/src/test/java/io/helidon/common/serviceloader/HelidonServiceLoaderTest.java b/common/service-loader/src/test/java/io/helidon/common/serviceloader/HelidonServiceLoaderTest.java
new file mode 100644
index 00000000000..9e4d99fb76a
--- /dev/null
+++ b/common/service-loader/src/test/java/io/helidon/common/serviceloader/HelidonServiceLoaderTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.common.serviceloader;
+
+import java.util.List;
+import java.util.ServiceLoader;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Unit test for {@link io.helidon.common.serviceloader.HelidonServiceLoader}.
+ */
+class HelidonServiceLoaderTest {
+ private static ServiceLoader javaLoader;
+
+ @BeforeAll
+ static void initClass() {
+ javaLoader = ServiceLoader.load(ServiceInterface.class);
+ }
+
+ @Test
+ void testJavaLoader() {
+ List loaded = HelidonServiceLoader.create(javaLoader).asList();
+
+ assertThat(loaded, hasSize(2));
+ assertThat(loaded.get(0).message(), is(ServiceImpl2.class.getName()));
+ assertThat(loaded.get(1).message(), is(ServiceImpl1.class.getName()));
+ }
+
+ @Test
+ void testCustomService() {
+ List loaded = HelidonServiceLoader.builder(javaLoader)
+ .addService(new ServiceImpl3())
+ .build()
+ .asList();
+
+ assertThat(loaded, hasSize(3));
+ assertThat(loaded.get(0).message(), is(ServiceImpl2.class.getName()));
+ assertThat(loaded.get(1).message(), is(ServiceImpl3.class.getName()));
+ assertThat(loaded.get(2).message(), is(ServiceImpl1.class.getName()));
+ }
+
+ @Test
+ void testCustomServiceWithCustomPrio() {
+ List loaded = HelidonServiceLoader.builder(javaLoader)
+ .addService(new ServiceImpl3(), 0)
+ .build()
+ .asList();
+
+ assertThat(loaded, hasSize(3));
+ assertThat(loaded.get(0).message(), is(ServiceImpl3.class.getName()));
+ assertThat(loaded.get(1).message(), is(ServiceImpl2.class.getName()));
+ assertThat(loaded.get(2).message(), is(ServiceImpl1.class.getName()));
+ }
+
+ @Test
+ void testExcludeService() {
+ List loaded = HelidonServiceLoader.builder(javaLoader)
+ .addService(new ServiceImpl3())
+ .addExcludedClass(ServiceImpl2.class)
+ .build()
+ .asList();
+
+ assertThat(loaded, hasSize(2));
+ assertThat(loaded.get(0).message(), is(ServiceImpl3.class.getName()));
+ assertThat(loaded.get(1).message(), is(ServiceImpl1.class.getName()));
+ }
+
+ @Test
+ void testExcludeServiceNames() {
+ List loaded = HelidonServiceLoader.builder(javaLoader)
+ .addService(new ServiceImpl3())
+ .addExcludedClassName(ServiceImpl1.class.getName())
+ .addExcludedClassName(ServiceImpl3.class.getName())
+ .build()
+ .asList();
+
+ assertThat(loaded, hasSize(1));
+ assertThat(loaded.get(0).message(), is(ServiceImpl2.class.getName()));
+ }
+
+ @Test
+ void testWithoutSystemServiceLoader() {
+ List loaded = HelidonServiceLoader.builder(javaLoader)
+ .addService(new ServiceImpl3())
+ .addService(new ServiceImpl2())
+ .useSystemServiceLoader(false)
+ .build()
+ .asList();
+
+ assertThat(loaded, hasSize(2));
+ assertThat(loaded.get(0).message(), is(ServiceImpl2.class.getName()));
+ assertThat(loaded.get(1).message(), is(ServiceImpl3.class.getName()));
+ }
+
+ @Test
+ void testUniqueImplementations() {
+ String TEST_STRING = "custom messsage";
+
+ List loaded = HelidonServiceLoader.builder(javaLoader)
+ .addService(new ServiceImpl2(TEST_STRING))
+ .build()
+ .asList();
+
+ assertThat(loaded, hasSize(2));
+ assertThat(loaded.get(0).message(), is(TEST_STRING));
+ assertThat(loaded.get(1).message(), is(ServiceImpl1.class.getName()));
+ }
+
+ @Test
+ void testNoUniqueImplementations() {
+ String TEST_STRING = "custom messsage";
+
+ List loaded = HelidonServiceLoader.builder(javaLoader)
+ .addService(new ServiceImpl2(TEST_STRING), 11)
+ .replaceImplementations(false)
+ .build()
+ .asList();
+
+ assertThat(loaded, hasSize(3));
+ assertThat(loaded.get(0).message(), is(TEST_STRING));
+ assertThat(loaded.get(1).message(), is(ServiceImpl2.class.getName()));
+ assertThat(loaded.get(2).message(), is(ServiceImpl1.class.getName()));
+ }
+
+ @Test
+ void testNegativePrioFails() {
+ assertThrows(IllegalArgumentException.class, () -> HelidonServiceLoader.builder(javaLoader)
+ .addService(new ServiceImpl2("something"), -11)
+ .replaceImplementations(false)
+ .build()
+ .asList());
+ }
+
+ @Test
+ void testZeropPrioWorks() {
+ HelidonServiceLoader.builder(javaLoader)
+ .addService(new ServiceImpl2("something"), 0)
+ .replaceImplementations(false)
+ .build()
+ .asList();
+ }
+
+}
+
diff --git a/common/service-loader/src/test/java/io/helidon/common/serviceloader/ServiceImpl1.java b/common/service-loader/src/test/java/io/helidon/common/serviceloader/ServiceImpl1.java
new file mode 100644
index 00000000000..5abf5d6cea2
--- /dev/null
+++ b/common/service-loader/src/test/java/io/helidon/common/serviceloader/ServiceImpl1.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.common.serviceloader;
+
+import javax.annotation.Priority;
+
+/**
+ * A service implementation.
+ */
+@Priority(47)
+public class ServiceImpl1 implements ServiceInterface {
+ @Override
+ public String message() {
+ return getClass().getName();
+ }
+}
diff --git a/common/service-loader/src/test/java/io/helidon/common/serviceloader/ServiceImpl2.java b/common/service-loader/src/test/java/io/helidon/common/serviceloader/ServiceImpl2.java
new file mode 100644
index 00000000000..8e540fc9a31
--- /dev/null
+++ b/common/service-loader/src/test/java/io/helidon/common/serviceloader/ServiceImpl2.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.common.serviceloader;
+
+import io.helidon.common.Prioritized;
+
+/**
+ * A service implementation.
+ */
+public class ServiceImpl2 implements ServiceInterface, Prioritized {
+ private final String message;
+
+ public ServiceImpl2() {
+ this.message = ServiceImpl2.class.getName();
+ }
+
+ public ServiceImpl2(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String message() {
+ return message;
+ }
+
+ @Override
+ public int priority() {
+ return 12;
+ }
+}
diff --git a/common/service-loader/src/test/java/io/helidon/common/serviceloader/ServiceImpl3.java b/common/service-loader/src/test/java/io/helidon/common/serviceloader/ServiceImpl3.java
new file mode 100644
index 00000000000..3249e01bbf3
--- /dev/null
+++ b/common/service-loader/src/test/java/io/helidon/common/serviceloader/ServiceImpl3.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.common.serviceloader;
+
+import javax.annotation.Priority;
+
+/**
+ * A service implementation.
+ */
+@Priority(22)
+public class ServiceImpl3 implements ServiceInterface {
+ @Override
+ public String message() {
+ return getClass().getName();
+ }
+}
diff --git a/common/service-loader/src/test/java/io/helidon/common/serviceloader/ServiceInterface.java b/common/service-loader/src/test/java/io/helidon/common/serviceloader/ServiceInterface.java
new file mode 100644
index 00000000000..e10398c4601
--- /dev/null
+++ b/common/service-loader/src/test/java/io/helidon/common/serviceloader/ServiceInterface.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.common.serviceloader;
+
+/**
+ * Testing Java Service loader service interface.
+ */
+public interface ServiceInterface {
+ String message();
+}
diff --git a/common/service-loader/src/test/resources/META-INF/services/io.helidon.common.serviceloader.ServiceInterface b/common/service-loader/src/test/resources/META-INF/services/io.helidon.common.serviceloader.ServiceInterface
new file mode 100644
index 00000000000..cef74a665ec
--- /dev/null
+++ b/common/service-loader/src/test/resources/META-INF/services/io.helidon.common.serviceloader.ServiceInterface
@@ -0,0 +1,18 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+io.helidon.common.serviceloader.ServiceImpl1
+io.helidon.common.serviceloader.ServiceImpl2
diff --git a/config/etcd/pom.xml b/config/etcd/pom.xml
index b7d5f5447d5..9c81092892b 100644
--- a/config/etcd/pom.xml
+++ b/config/etcd/pom.xml
@@ -133,15 +133,6 @@
org.xolstice.maven.pluginsprotobuf-maven-plugin
-
-
- com.google.protobuf:protoc:${version.lib.protobuf.java}:exe:${os.detected.classifier}
-
- grpc-java
-
- io.grpc:protoc-gen-grpc-java:${version.lib.grpc}:exe:${os.detected.classifier}
-
-
diff --git a/config/yaml/src/main/java9/module-info.java b/config/yaml/src/main/java9/module-info.java
index 7b4fef1ea17..add8dcfaf84 100644
--- a/config/yaml/src/main/java9/module-info.java
+++ b/config/yaml/src/main/java9/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@
requires java.logging;
- requires snakeyaml;
+ requires org.yaml.snakeyaml;
requires transitive io.helidon.config;
requires io.helidon.common;
diff --git a/docs-internal/context.md b/docs-internal/context.md
new file mode 100644
index 00000000000..e08a16152da
--- /dev/null
+++ b/docs-internal/context.md
@@ -0,0 +1,90 @@
+# Helidon Context Support Proposal
+
+Provide a way to propagate Context across thread boundaries.
+We already use the WebServer request context to store and retrieve
+information (such as in security).
+
+This proposal would allow us to use the context:
+
+- in any thread managed (or wrapped) by Helidon
+- to propagate information all the way from server inbound
+ request to a client outbound request
+- to handle correctly things such as parent SpanContext in tracing
+ where we currently have access only to the overall WebServer
+ parent span
+
+## Problem
+
+We currently have context available only when we can reach
+ServerRequest. There are cases, where we need to propagate
+context information and where dependency on WebServer is not desired,
+such as in Helidon DB.
+Also with introduction of gRPC server, we have another source
+of contexts, that should not depend on http modules.
+
+## Proposal
+
+This proposal defines:
+
+- new common API
+- updates to existing modules
+
+### New API
+
+A new module `helidon-common-context` is to be created.
+Public API consists of:
+
+- `io.helidon.common.context.Context` - a copy of existing `ContextualRegistry` in `http`,
+ to be used as the main point of registering and retrieving contextual values
+- `io.helidon.common.context.Contexts` - a utility class with helpful static methods
+ to work with `Context`:
+ - `Optional context()`: to retrieve current context (if there is one)
+ - `ExecutorService wrap(ExecutorService)`: to wrap any executor service and create a context-aware executor service
+ - `ScheduledExecutorService wrap(ScheduledExecutorService)`: to wrap any scheduled executor service and create a
+ context-aware scheduled executor service
+ - `void inContext(Context, Runnable)`: to execute a runnable in a context in current thread
+ - ` T inContext(Context, Callable)`: to execute a callable in a context in current thread
+- `io.helidon.common.context.ExecutorException` - unchecked exception to use in this module
+
+The new implementation can be source code compatible with previous
+ Helidon versions, as long as we keep the `ContextualRegistry` in
+ our `http` module and it extends the new `Context` interface.
+
+### Updates to existing modules
+
+Each module that creates a new context (e.g. WebServer and gRPC server when starting processing of a new request)
+ should execute subsequent methods in that context using `Contexts.inContext()`.
+
+Each module that hands processing over to an executor service should wrap that
+executor service using `Contexts.wrap()`.
+
+Modules that are interested in using the context should retrieve
+the current context using `Contexts.context()`.
+
+## Examples
+
+New context created (`RequestRouting.next()`):
+```java
+Contexts.inContext(nextRequest.context(), () -> nextItem.handlerRoute
+ .handler()
+ .accept(nextRequest, nextResponse));
+```
+
+Processing through an executor service (`JerseySupport`):
+```java
+ExecutorService executorService = (service != null)
+ ? service
+ : Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
+
+this.service = Contexts.wrap(executorService);
+
+//...
+service.submit(() -> {...});
+```
+
+Using the context (`HelidonDb`):
+```java
+Executors.context()
+ .ifPresent(context -> interceptors
+ .forEach(interceptor -> interceptor.statement(context, statementName, statement, statementFuture)));
+```
\ No newline at end of file
diff --git a/docs/src/main/docs/extensions/01_overview.adoc b/docs/src/main/docs/extensions/01_overview.adoc
index 618f70fb7b0..e2330e1e562 100644
--- a/docs/src/main/docs/extensions/01_overview.adoc
+++ b/docs/src/main/docs/extensions/01_overview.adoc
@@ -51,4 +51,11 @@ Create and inject a Jedis pool in your application code.
Create and inject an Oracle Cloud Infrastructure Object Storage client in your
application code.
--
+
+[CARD]
+.Java Transaction API objects
+[link=extensions/05_cdi_jta.adoc]
+--
+Use the Java Transaction API in your application code.
+--
====
diff --git a/docs/src/main/docs/extensions/05_cdi_jta.adoc b/docs/src/main/docs/extensions/05_cdi_jta.adoc
new file mode 100644
index 00000000000..ea04e913efd
--- /dev/null
+++ b/docs/src/main/docs/extensions/05_cdi_jta.adoc
@@ -0,0 +1,84 @@
+///////////////////////////////////////////////////////////////////////////////
+
+ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+///////////////////////////////////////////////////////////////////////////////
+
+= CDI extension for JTA
+:description: Helidon CDI extension for JTA
+:keywords: helidon, java, microservices, microprofile, extensions, cdi, jta
+
+This https://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#spi[CDI
+portable extension] provides support for JTA (Java Transaction API)
+transactions in your Helidon MicroProfile applications.
+
+== Prerequsites
+
+Declare the following dependency fragment in your project's `pom.xml`:
+
+[source,xml]
+----
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-jta-weld
+ runtime
+
+
+
+ javax.transaction
+ javax.transaction-api
+ provided
+
+----
+
+== Declaring a method to be transactional
+
+The following example shows how to declare a transactional method.
+
+[source,java]
+.Transactional method declaration
+----
+@Transactional(Transactional.TxType.REQUIRED)
+public void doSomethingTransactionally() {
+
+}
+----
+
+The extension ensures that a transaction is started before and
+committed after the method executes. If the method throws an
+exception, the transaction will be rolled back.
+
+You can further specify the transactional behavior of the extension by
+using different instances of the `Transactional` annotation. For more
+information, see the
+https://static.javadoc.io/javax.transaction/javax.transaction-api/1.2/javax/transaction/Transactional.html[`Transactional`
+annotation documentation].
+
+Transactional method support is implemented by CDI interception
+facilities. Among other things, this means that the method to which
+you apply the `Transactional` annotation must not be `private` and
+must in all other ways be a _business method_. See the
+https://jcp.org/aboutJava/communityprocess/mrel/jsr318/index3.html[Java
+Interceptors specification] for more details.
+
+During a transactional method invocation, the extension makes the
+following objects available for injection via the `Inject` annotation:
+
+* https://static.javadoc.io/javax.transaction/javax.transaction-api/1.2/javax/transaction/UserTransaction.html[`UserTransaction`]
+* https://static.javadoc.io/javax.transaction/javax.transaction-api/1.2/javax/transaction/Transaction.html[`Transaction`]
+* https://static.javadoc.io/javax.transaction/javax.transaction-api/1.2/javax/transaction/UserTransactionManager.html[`TransactionManager`]
+* https://static.javadoc.io/javax.transaction/javax.transaction-api/1.2/javax/transaction/UserTransactionSynchronizationRegistry.html[`TransactionSynchronizationRegistry`]
+
+
diff --git a/docs/src/main/docs/grpc/01_introduction.adoc b/docs/src/main/docs/grpc/01_introduction.adoc
new file mode 100644
index 00000000000..91ce8e196da
--- /dev/null
+++ b/docs/src/main/docs/grpc/01_introduction.adoc
@@ -0,0 +1,80 @@
+///////////////////////////////////////////////////////////////////////////////
+
+ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+///////////////////////////////////////////////////////////////////////////////
+
+:pagename: grpc-server-introduction
+:description: Helidon gRPC Server Introduction
+:keywords: helidon, grpc, java
+
+= gRPC Server Introduction
+
+Helidon gRPC Server provides a framework for creating link:http://grpc.io/[gRPC] applications.
+
+=== _Experimental Feature_
+The Helidon gRPC feature is currently experimental and the APIs are subject to changes until gRPC support is stabilized.
+
+== Quick Start
+
+Here is the code for a minimalist gRPC application that runs on a default port (1408):
+
+[source,java]
+----
+ public static void main(String[] args) throws Exception {
+ GrpcServer grpcServer = GrpcServer
+ .create(GrpcRouting.builder()
+ .register(new HelloService()) // <1>
+ .build())
+ .start() // <2>
+ .toCompletableFuture()
+ .get(10, TimeUnit.SECONDS); // <3>
+
+ System.out.println("gRPC Server started at: http://localhost:" + grpcServer.port()); // <4>
+ }
+
+ static class HelloService implements GrpcService { <5>
+ @Override
+ public void update(ServiceDescriptor.Rules rules) {
+ rules.unary("SayHello", ((request, responseObserver) -> complete(responseObserver, "Hello World!"))); // <6>
+ }
+ }
+----
+
+<1> Register gRPC service.
+<2> Start the server.
+<3> Wait for the server to start while throwing possible errors as exceptions.
+<4> The server is bound to a default port (1408).
+<5> Implement the simplest possible gRPC service.
+<6> Add unary method `HelloService/SayHello` to the service definition.
+
+The example above deploys a very simple service to the gRPC server that by default uses Java serialization to marshall
+requests and responses. We will look into deployment of "standard" gRPC services that use Protobuf for request and
+response marshalling, as well as how you can configure custom marshallers, later in this document.
+
+== Maven Coordinates
+
+The <> page describes how you
+should declare dependency management for Helidon applications. Then declare the following dependency in your project:
+
+[source,xml,subs="verbatim,attributes"]
+----
+
+ io.helidon.grpc
+ helidon-grpc-server
+
+----
+
+<1> Dependency on gRPC Server.
diff --git a/docs/src/main/docs/grpc/02_configuration.adoc b/docs/src/main/docs/grpc/02_configuration.adoc
new file mode 100644
index 00000000000..bc5a08319cf
--- /dev/null
+++ b/docs/src/main/docs/grpc/02_configuration.adoc
@@ -0,0 +1,69 @@
+///////////////////////////////////////////////////////////////////////////////
+
+ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+///////////////////////////////////////////////////////////////////////////////
+
+:javadoc-base-url-api: {javadoc-base-url}?io/helidon/grpc/server
+:pagename: grpc-server-configuration
+:description: Helidon gRPC Server Configuration
+:keywords: helidon, grpc, java, configuration
+
+= gRPC Server Configuration
+
+Configure the gRPC Server using the Helidon configuration framework, either programmatically
+or via a configuration file.
+
+== Configuring the gRPC Server in your code
+
+The easiest way to configure the gRPC Server is in your application code.
+
+[source,java]
+----
+GrpcServerConfiguration configuration = GrpcServerConfiguration.builder()
+ .port(8080)
+ .build();
+GrpcServer grpcServer = GrpcServer.create(configuration, routing);
+----
+
+== Configuring the gRPC Server in a configuration file
+
+You can also define the configuration in a file.
+
+[source,hocon]
+.GrpcServer configuration file `application.yaml`
+----
+grpcserver:
+ port: 3333
+----
+
+Then, in your application code, load the configuration from that file.
+
+[source,java]
+.GrpcServer initialization using the `application.conf` file located on the classpath
+----
+GrpcServerConfiguration configuration = GrpcServerConfiguration.create(
+ Config.builder()
+ .sources(classpath("application.conf"))
+ .build());
+
+GrpcServer grpcServer = GrpcServer.create(configuration, routing);
+----
+
+== Configuration options
+
+See all configuration options
+ link:{javadoc-base-url-api}/GrpcServerConfiguration.html[here].
+
diff --git a/docs/src/main/docs/grpc/03_routing.adoc b/docs/src/main/docs/grpc/03_routing.adoc
new file mode 100644
index 00000000000..0bd14cc3707
--- /dev/null
+++ b/docs/src/main/docs/grpc/03_routing.adoc
@@ -0,0 +1,102 @@
+///////////////////////////////////////////////////////////////////////////////
+
+ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+///////////////////////////////////////////////////////////////////////////////
+
+:pagename: grpc-server-routing
+:description: Helidon gRPC Server Routing
+:keywords: helidon, grpc, java
+
+= gRPC Server Routing
+
+Unlike Webserver, which allows you to route requests based on path expression
+and the HTTP verb, gRPC server always routes requests based on the service and
+method name. This makes routing configuration somewhat simpler -- all you need
+to do is register your services:
+
+[source,java]
+----
+ private static GrpcRouting createRouting(Config config) {
+ return GrpcRouting.builder()
+ .register(new GreetService(config)) // <1>
+ .register(new EchoService()) // <2>
+ .register(new MathService()) // <3>
+ .build();
+ }
+----
+
+<1> Register `GreetService` instance.
+<2> Register `EchoService` instance.
+<3> Register `MathService` instance.
+
+Both "standard" gRPC services that implement `io.grpc.BindableService` interface
+(typically implemented by extending generated server-side stub and overriding
+its methods), and Helidon gRPC services that implement
+`io.helidon.grpc.server.GrpcService` interface can be registered.
+
+The difference is that Helidon gRPC services allow you to customize behavior
+down to the method level, and provide a number of useful helper methods that
+make service implementation easier, as we'll see in a moment.
+
+== Customizing Service Definitions
+
+When registering a service, regardless of its type, you can customize its
+descriptor by providing configuration consumer as a second argument to the
+`register` method.
+
+This is particularly useful when registering standard `BindableService`
+instances, as it allows you to add certain Helidon-specific behaviors, such as
+<<06_health_checks.adoc, health checks>> and <<07_metrics.adoc, metrics>> to them:
+
+[source,java]
+----
+ private static GrpcRouting createRouting(Config config) {
+ return GrpcRouting.builder()
+ .register(new GreetService(config))
+ .register(new EchoService(), service -> {
+ service.healthCheck(CustomHealthChecks::echoHealthCheck) // <1>
+ .metered(); // <2>
+ })
+ .build();
+ }
+----
+
+<1> Add custom health check to the service.
+<2> Specify that all the calls to service methods should be metered.
+
+== Specifying Global Interceptors
+
+`GrpcRouting` also allows you to specify <<05_interceptors.adoc, custom interceptors>>
+that will be applied to all registered services.
+
+This is useful to configure features such as tracing, security and metrics collection,
+and we provide built-in interceptors for those purposes that you can simply register
+with the routing definition:
+
+[source,java]
+----
+ private static GrpcRouting createRouting(Config config) {
+ return GrpcRouting.builder()
+ .intercept(GrpcMetrics.timed()) // <1>
+ .register(new GreetService(config))
+ .register(new EchoService())
+ .register(new MathService())
+ .build();
+ }
+----
+
+<1> Register `GrpcMetrics` interceptor that will collect timers for all methods of
+ all services (but can be overridden at the individual service or even method level).
diff --git a/docs/src/main/docs/grpc/04_service_implementation.adoc b/docs/src/main/docs/grpc/04_service_implementation.adoc
new file mode 100644
index 00000000000..d1ae3b962dd
--- /dev/null
+++ b/docs/src/main/docs/grpc/04_service_implementation.adoc
@@ -0,0 +1,163 @@
+///////////////////////////////////////////////////////////////////////////////
+
+ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+///////////////////////////////////////////////////////////////////////////////
+
+:javadoc-base-url-api: {javadoc-base-url}?io/helidon/grpc/server
+:pagename: grpc-server-service-implementation
+:description: Helidon gRPC Service Implementation
+:keywords: helidon, grpc, java
+
+= Service Implementation
+
+While Helidon gRPC Server allows you to deploy any standard gRPC service that
+implements `io.grpc.BindableService` interface, including services generated
+from the Protobuf IDL files (and even allows you to customize them to a certain
+extent), using Helidon gRPC framework to implement your services has a number of
+benefits:
+
+* It allows you to define both HTTP and gRPC services using similar programming
+ model, simplifying learning curve for developers.
+
+* It provides a number of helper methods that make service implementation
+ significantly simpler.
+
+* It allows you to configure some of the Helidon value-added features, such
+ as <<08_security.adoc, security>> and <<07_metrics.adoc, metrics collection>>
+ down to the method level.
+
+* It allows you to easily specify custom marshaller for requests and
+ responses if Protobuf does not satisfy your needs.
+
+* It provides built in support for <<06_health_checks.adoc, health checks>>.
+
+== Service Implementation Basics
+
+At the very basic level, all you need to do in order to implement a Helidon
+gRPC service is create a class that implements `io.helidon.grpc.server.GrpcService`
+interface and define one or more methods for the service:
+
+[source,java]
+----
+class EchoService implements GrpcService {
+
+ @Override
+ public void update(ServiceDescriptor.Rules rules) {
+ rules.unary("Echo", this::echo); // <1>
+ }
+
+ /**
+ * Echo the message back to the caller.
+ *
+ * @param request the echo request containing the message to echo
+ * @param observer the response observer
+ */
+ public void echo(String request, StreamObserver observer) { // <2>
+ complete(observer, request); // <3>
+ }
+}
+----
+
+<1> Define unary method `Echo` and map it to the `this::echo` handler.
+<2> Create a handler for the `Echo` method.
+<3> Send the request string back to the client by completing response observer.
+
+NOTE: The `complete` method shown in the example above is just one of many helper
+ methods available in the `GrpcService` class. See the full list
+ link:{javadoc-base-url-api}/GrpcService.html[here].
+
+The example above implements a service with a single unary method, which will be
+exposed at the `EchoService/Echo' endpoint. The service does not explicitly define
+a marshaller for requests and responses, so Java serialization will be used as a
+default.
+
+Unfortunately, this implies that you will have to implement clients by hand and
+configure them to use the same marshaller as the server. Obviously, one of the
+major selling points of gRPC is that it makes it easy to generate clients for a
+number of languages (as long as you use Protobuf for marshalling), so let's see
+how we would implement Protobuf enabled Helidon gRPC service.
+
+== Implementing Protobuf Services
+
+In order to implement Protobuf-based service, you would follow the official
+link:https://grpc.io/docs/quickstart/java.html[instructions] on the gRPC
+web site, which boil down to the following:
+
+==== Define the Service IDL
+
+For this example, we will re-implement the `EchoService` above as a Protobuf
+service in `echo.proto` file.
+
+[source, proto]
+----
+syntax = "proto3";
+option java_package = "org.example.services.echo";
+
+service EchoService {
+ rpc Echo (EchoRequest) returns (EchoResponse) {}
+}
+
+message EchoRequest {
+ string message = 1;
+}
+
+message EchoResponse {
+ string message = 1;
+}
+----
+
+Based on this IDL, the gRPC compiler will generate message classes (`EchoRequest`
+and `EchoResponse`), client stubs that can be used to make RPC calls to the server,
+as well as the base class for the server-side service implementation.
+
+We can ignore the last one, and implement the service using Helidon gRPC framework
+instead.
+
+==== Implement the Service
+
+The service implementation will be very similar to our original implementation:
+
+[source,java]
+----
+class EchoService implements GrpcService {
+
+ @Override
+ public void update(ServiceDescriptor.Rules rules) {
+ rules.proto(Echo.getDescriptor()) // <1>
+ .unary("Echo", this::echo); // <2>
+ }
+
+ /**
+ * Echo the message back to the caller.
+ *
+ * @param request the echo request containing the message to echo
+ * @param observer the response observer
+ */
+ public void echo(Echo.EchoRequest request, StreamObserver observer) { // <3>
+ String message = request.getMessage(); // <4>
+ Echo.EchoResponse response = Echo.EchoResponse.newBuilder().setMessage(message).build(); // <5>
+ complete(observer, response); // <6>
+ }
+}
+----
+
+<1> Specify proto descriptor in order to provide necessary type information and
+ enable Protobuf marshalling.
+<2> Define unary method `Echo` and map it to the `this::echo` handler.
+<3> Create a handler for the `Echo` method, using Protobuf message types for request and response.
+<4> Extract message string from the request.
+<5> Create the response containing extracted message.
+<6> Send the response back to the client by completing response observer.
diff --git a/docs/src/main/docs/grpc/05_interceptors.adoc b/docs/src/main/docs/grpc/05_interceptors.adoc
new file mode 100644
index 00000000000..4def7c98793
--- /dev/null
+++ b/docs/src/main/docs/grpc/05_interceptors.adoc
@@ -0,0 +1,123 @@
+///////////////////////////////////////////////////////////////////////////////
+
+ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+///////////////////////////////////////////////////////////////////////////////
+
+:pagename: grpc-server-interceptors
+:description: Helidon gRPC Service Interceptors
+:keywords: helidon, grpc, java
+
+= Interceptors
+
+Helidon gRPC allows you to configure standard `io.grpc.ServerInterceptor`s.
+
+For example, you could implement an interceptor that logs each RPC call:
+
+[source,java]
+----
+class LoggingInterceptor implements ServerInterceptor { // <1>
+
+ private static final Logger LOG = Logger.getLogger(LoggingInterceptor.class.getName());
+
+ @Override
+ public ServerCall.Listener interceptCall(ServerCall call,
+ Metadata metadata,
+ ServerCallHandler handler) {
+
+ LOG.info(() -> "CALL: " + call.getMethodDescriptor()); // <2>
+ return handler.startCall(call, metadata); // <3>
+ }
+}
+----
+
+<1> Implement `io.grpc.ServerInterceptor`
+<2> Implement the logging logic
+<3> Start intercepted call
+
+== Registering Interceptors
+
+You can register interceptors globally, in which case they will be applied to all
+methods of all services, by simply adding them to the `GrpcRouting` instance:
+
+[source,java]
+----
+private static GrpcRouting createRouting(Config config) {
+ return GrpcRouting.builder()
+ .intercept(new LoggingInterceptor()) // <1>
+ .register(new GreetService(config))
+ .register(new EchoService())
+ .build();
+}
+----
+
+<1> Adds `LoggingInterceptor` to all methods of `GreetService` and `EchoService`
+
+You can also register an interceptor for a specific service, either by implementing
+`GrpcService.update` method:
+
+[source,java]
+----
+public class MyService implements GrpcService {
+
+ @Override
+ public void update(ServiceDescriptor.Rules rules) {
+ rules.intercept(new LoggingInterceptor()) // <1>
+ .unary("MyMethod", this::myMethod);
+ }
+
+ private void myMethod(ReqT request, StreamObserver observer) {
+ // do something
+ }
+}
+----
+
+<1> Adds `LoggingInterceptor` to all methods of `MyService`
+
+Or by configuring `ServiceDescriptor` externally, when creating `GrpcRouting`, which
+allows you to add interceptors to plain `io.grpc.BindableService` services as well:
+
+[source,java]
+----
+private static GrpcRouting createRouting(Config config) {
+ return GrpcRouting.builder()
+ .register(new GreetService(config), cfg -> cfg.intercept(new LoggingInterceptor())) // <1>
+ .register(new EchoService())
+ .build();
+}
+----
+
+<1> Adds `LoggingInterceptor` to all methods of `GreetService` only
+
+Finally, you can also register an interceptor at the method level:
+
+[source,java]
+----
+public class MyService implements GrpcService {
+
+ @Override
+ public void update(ServiceDescriptor.Rules rules) {
+ rules.unary("MyMethod",
+ this::myMethod,
+ cfg -> cfg.intercept(new LoggingInterceptor())); // <1>
+ }
+
+ private void myMethod(ReqT request, StreamObserver observer) {
+ // do something
+ }
+}
+----
+
+<1> Adds `LoggingInterceptor` to `MyService::MyMethod` only
diff --git a/docs/src/main/docs/grpc/06_health_checks.adoc b/docs/src/main/docs/grpc/06_health_checks.adoc
new file mode 100644
index 00000000000..2d54bd0d140
--- /dev/null
+++ b/docs/src/main/docs/grpc/06_health_checks.adoc
@@ -0,0 +1,116 @@
+///////////////////////////////////////////////////////////////////////////////
+
+ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+///////////////////////////////////////////////////////////////////////////////
+
+:pagename: grpc-server-health-checks
+:description: Helidon gRPC Service Health Checks
+:keywords: helidon, grpc, java
+
+= Service Health Checks
+
+Helidon gRPC services provide a built-in support for Helidon Health Checks.
+
+Unless a custom health check is implemented by the service developer, each service
+deployed to the gRPC server will be provisioned with a default health check, which
+always returns status of `UP`.
+
+This allows all services, including the ones that don't have a meaningful health check,
+to show up in the health report (or to be queried for health) without service developer
+having to do anything.
+
+However, services that do need custom health checks can easily define one,
+directly within `GrpcService` implementation:
+
+[source,java]
+----
+public class MyService implements GrpcService {
+
+ @Override
+ public void update(ServiceDescriptor.Rules rules) {
+ rules.unary("MyMethod", this::myMethod)
+ .healthCheck(this::healthCheck); // <1>
+ }
+
+ private HealthCheckResponse healthCheck() {
+ boolean fUp = isMyServiceUp(); // <2>
+ return HealthCheckResponse
+ .named(name()) // <3>
+ .state(fUp) // <4>
+ .withData("ts", System.currentTimeMillis()) // <5>
+ .build();
+ }
+
+ private void myMethod(ReqT request, StreamObserver observer) {
+ // do something
+ }
+}
+----
+
+<1> Configure a custom health check for the service
+<2> Determine service status
+<3> Use service name as a health check name for consistency
+<4> Use determined service status
+<5> Optionally, provide additional metadata
+
+You can also define custom health check for an existing service, including plain
+`io.grpc.BindableService` implementations, using service configurer inside the
+`GrpcRouting` deefinition:
+
+[source,java]
+----
+private static GrpcRouting createRouting() {
+ return GrpcRouting.builder()
+ .register(new EchoService(), cfg -> cfg.healthCheck(MyCustomHealthChecks::echoHealthCheck)) // <1>
+ .build();
+}
+----
+
+<1> Configure custom health check for an existing or legacy service
+
+== Exposing Health Checks
+
+All gRPC service health checks are managed by the Helidon gRPC Server, and are
+automatically exposed to the gRPC clients using custom implementation of the
+standard gRPC `HealthService` API.
+
+However, they can also be exposed to REST clients via standard Helidon/Microprofile
+`/health` endpoint:
+
+[source,java]
+----
+ GrpcServer grpcServer = GrpcServer.create(grpcServerConfig(), createRouting(config)); // <1>
+ grpcServer.start(); // <2>
+
+ HealthSupport health = HealthSupport.builder()
+ .add(grpcServer.healthChecks()) // <3>
+ .build();
+
+ Routing routing = Routing.builder()
+ .register(health) // <4>
+ .build();
+
+ WebServer.create(webServerConfig(), routing).start(); // <5>
+----
+
+<1> Create `GrpcServer` instance
+<2> Start gRPC server, which will deploy all services and register default and custom health checks
+<3> Add gRPC server managed health checks to `HealthSupport` instance
+<4> Add `HealthSupport` to the web server routing definition
+<5> Create and start web server
+
+All gRPC health checks will now be available via `/health` REST endpoint, in
+addition to the standard gRPC `HealthService`
diff --git a/docs/src/main/docs/grpc/07_metrics.adoc b/docs/src/main/docs/grpc/07_metrics.adoc
new file mode 100644
index 00000000000..79f92fbcac7
--- /dev/null
+++ b/docs/src/main/docs/grpc/07_metrics.adoc
@@ -0,0 +1,199 @@
+///////////////////////////////////////////////////////////////////////////////
+
+ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+///////////////////////////////////////////////////////////////////////////////
+
+:pagename: grpc-server-metrics
+:description: Helidon gRPC Service Metrics
+:keywords: helidon, grpc, java
+
+= Service Metrics
+
+Helidon gRPC Server has built-in support for metrics capture, which allows
+service developers to easily enable application-level metrics for their services.
+
+== Enabling Metrics Capture
+
+By default, gRPC Server only captures two vendor-level metrics: `grpc.request.count`
+and `grpc.request.meter`. These metrics provide aggregate view of requests across
+all services, and serve as an indication of the overall server load.
+
+However, users can enable more fine grained metrics by simply configuring a built-in
+`GrpcMetrics` interceptor within the routing:
+
+[source,java]
+----
+ private static GrpcRouting createRouting(Config config) {
+ return GrpcRouting.builder()
+ .intercept(GrpcMetrics.timed()) // <1>
+ .register(new GreetService(config))
+ .register(new EchoService())
+ .build();
+ }
+----
+
+<1> Capture metrics for all methods of all services as a `timer`
+
+In the example above we have chosen to create and keep a `timer` metric type for
+each method of each service. Alternatively, we could've chosen to use a
+`counter`, `meter` or a `histogram` instead.
+
+== Overriding Metrics Capture
+
+While global metrics capture is certainly useful, it is not always sufficient.
+Keeping a separate `timer` for each gRPC method may be an overkill, so the user
+could decide to use a lighter-weight metric type, such as `counter` or a `meter`.
+
+However, she may still want to enable `histogram` or a `timer` for some services,
+or even only some methods of some services.
+
+This can be easily accomplished by overriding the type of the captured metric at
+either service or the method level:
+
+[source,java]
+----
+ private static GrpcRouting createRouting(Config config) {
+ return GrpcRouting.builder()
+ .intercept(GrpcMetrics.counted()) // <1>
+ .register(new MyService())
+ .build();
+ }
+
+ public static class MyService implements GrpcService {
+
+ @Override
+ public void update(ServiceDescriptor.Rules rules) {
+ rules
+ .intercept(GrpcMetrics.metered()) // <2>
+ .unary("MyMethod", this::myMethod,
+ cfg -> cfg.intercept(GrpcMetrics.timer())) // <3>
+ }
+
+ private void myMethod(ReqT request, StreamObserver observer) {
+ // do something
+ }
+ }
+----
+
+<1> Use `counter` for all methods of all services, unless overridden
+<2> Use `meter` for all methods of `MyService`
+<3> Use `timer` for `MyService::MyMethod`
+
+== Exposing Metrics Externally
+
+Collected metrics are stored in the standard Helidon Metric Registries, such as vendor and
+application registry, and can be exposed via standard `/metrics` REST API.
+
+[source,java]
+----
+ Routing routing = Routing.builder()
+ .register(MetricsSupport.create()) // <1>
+ .build();
+
+ WebServer.create(webServerConfig(), routing) // <2>
+ .start()
+----
+
+<1> Add `MetricsSupport` instance to web server routing
+<2> Create and start Helidon web server
+
+See <> documentation for more details.
+
+== Specifying Metric Meta-data
+
+Helidon metrics contain meta-data such as tags, a description, units etc. It is possible to
+add this additional meta-data when specifying the metrics.
+
+=== Adding Tags
+
+To add tags to a metric a `Map` of key/value tags can be supplied.
+For example:
+[source,java]
+----
+Map tagMap = new HashMap<>();
+tagMap.put("keyOne", "valueOne");
+tagMap.put("keyTwo", "valueTwo");
+
+GrpcRouting routing = GrpcRouting.builder()
+ .intercept(GrpcMetrics.counted().tags(tagMap)) // <1>
+ .register(new MyService())
+ .build();
+----
+
+<1> the `tags()` method is used to add the `Map` of tags to the metric.
+
+=== Adding a Description
+
+A meaningful description can be added to a metric:
+For example:
+[source,java]
+----
+GrpcRouting routing = GrpcRouting.builder()
+ .intercept(GrpcMetrics.counted().description("Something useful")) // <1>
+ .register(new MyService())
+ .build();
+----
+
+<1> the `description()` method is used to add the description to the metric.
+
+=== Adding Metric Units
+
+A units value can be added to the Metric:
+For example:
+[source,java]
+----
+GrpcRouting routing = GrpcRouting.builder()
+ .intercept(GrpcMetrics.timed().units(MetricUnits.SECONDS)) // <1>
+ .register(new MyService())
+ .build();
+----
+
+<1> the `units()` method is used to add the metric units to the metric.
+Typically the units value is one of the constants from `org.eclipse.microprofile.metrics.MetricUnits` class.
+
+== Overriding the Metric Name
+
+By default the metric name is the gRPC service name followed by a dot ('.') followed by the method name.
+It is possible to supply a function that can be used to override the default behaviour.
+
+The function should implement the `io.helidon.grpc.metrics.GrpcMetrics.NamingFunction` interface
+[source,java]
+----
+ @FunctionalInterface
+ public interface NamingFunction {
+ /**
+ * Create a metric name.
+ *
+ * @param service the service descriptor
+ * @param methodName the method name
+ * @param metricType the metric type
+ * @return the metric name
+ */
+ String createName(ServiceDescriptor service, String methodName, MetricType metricType);
+ }
+----
+This is a functional interface so lambda can be used too.
+
+For example:
+[source,java]
+----
+GrpcRouting routing = GrpcRouting.builder()
+ .intercept(GrpcMetrics.counted()
+ .nameFunction((svc, method, metric) -> "grpc." + service.name() + '.' + method) // <1>
+----
+<1> the `NamingFunction` is just a lambda that returns the concatenated service name and method name
+with the prefix `grpc.` So for a service "Foo", method "bar" the above example would produce a name
+"grpc.Foo.bar".
diff --git a/docs/src/main/docs/grpc/08_security.adoc b/docs/src/main/docs/grpc/08_security.adoc
new file mode 100644
index 00000000000..c60a5a39f81
--- /dev/null
+++ b/docs/src/main/docs/grpc/08_security.adoc
@@ -0,0 +1,172 @@
+///////////////////////////////////////////////////////////////////////////////
+
+ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+///////////////////////////////////////////////////////////////////////////////
+
+:description: Helidon Security gRPC integration
+:keywords: helidon, grpc, security
+
+= gRPC Server Security
+Security integration of the <>
+
+[source,xml]
+.Maven Dependency
+----
+
+ io.helidon.security.integration
+ helidon-security-integration-grpc
+
+----
+
+==== Bootstrapping
+
+There are two steps to configure security with gRPC server:
+
+1. Create security instance and register it with server
+2. Protect gRPC services of server with various security features
+
+[source,java]
+.Example using builders
+----
+// gRPC server's routing
+GrpcRouting.builder()
+ // This is step 1 - register security instance with gRPC server processing
+ // security - instance of security either from config or from a builder
+ // securityDefaults - default enforcement for each service that has a security definition
+ .intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate()))
+ // this is step 2 - protect a service
+ // register and protect this service with authentication (from defaults) and role "user"
+ .register(greetService, GrpcSecurity.rolesAllowed("user"))
+ .build();
+----
+
+[source,java]
+.Example using builders for more fine grained method level security
+----
+// create the service descriptor
+ServiceDescriptor greetService = ServiceDescriptor.builder(new GreetService())
+ // Add an instance of gRPC security that will apply to all methods of
+ // the service - in this case require the "user" role
+ .intercept(GrpcSecurity.rolesAllowed("user"))
+ // Add an instance of gRPC security that will apply to the "SetGreeting"
+ // method of the service - in this case require the "admin" role
+ .intercept("SetGreeting", GrpcSecurity.rolesAllowed("admin"))
+ .build();
+
+// Create the gRPC server's routing
+GrpcRouting.builder()
+ // This is step 1 - register security instance with gRPC server processing
+ // security - instance of security either from config or from a builder
+ // securityDefaults - default enforcement for each service that has a security definition
+ .intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate()))
+ // this is step 2 - add the service descriptor
+ .register(greetService)
+ .build();
+----
+
+[source,java]
+.Example using configuration
+----
+GrpcRouting.builder()
+ // helper method to load both security and gRPC server security from configuration
+ .intercept(GrpcSecurity.create(config))
+ // continue with gRPC server route configuration...
+ .register(new GreetService())
+ .build();
+----
+
+[source,conf]
+.Example using configuration - configuration (HOCON)
+----
+# This may change in the future - to align with gRPC server configuration,
+# once it is supported
+security
+ grpc-server:
+ # Configuration of integration with gRPC server
+ defaults:
+ authenticate: true
+ # Configuration security for individual services
+ services:
+ - name: "GreetService"
+ defaults:
+ roles-allowed: ["user"]
+ # Configuration security for individual methods of the service
+ methods:
+ - name: "SetGreeting"
+ roles-allowed: ["admin"]
+----
+
+==== Outbound security
+Outbound security covers three scenarios:
+
+* Calling a secure gRPC service from inside a gRPC service method handler
+* Calling a secure gRPC service from inside a web server method handler
+* Calling a secure web endpoint from inside a gRPC service method handler
+
+Within each scenario credentials can be propagated if the gRPC/http method
+handler is executing within a security context or credentials can be overridden
+to provide a different set of credentials to use to call the outbound endpoint.
+
+[source,java]
+.Example calling a secure gRPC service from inside a gRPC service method handler
+----
+// Obtain the SecurityContext from the current gRPC call Context
+SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get();
+
+// Create a gRPC CallCredentials that will use the current request's
+// security context to configure outbound credentials
+GrpcClientSecurity clientSecurity = GrpcClientSecurity.create(securityContext);
+
+// Create the gRPC stub using the CallCredentials
+EchoServiceGrpc.EchoServiceBlockingStub stub = noCredsEchoStub.withCallCredentials(clientSecurity);
+----
+
+[source,java]
+.Example calling a secure gRPC service from inside a web server method handler
+----
+private static void propagateCredentialsWebRequest(ServerRequest req, ServerResponse res) {
+ try {
+ // Create a gRPC CallCredentials that will use the current request's
+ // security context to configure outbound credentials
+ GrpcClientSecurity clientSecurity = GrpcClientSecurity.create(req);
+
+ // Create the gRPC stub using the CallCredentials
+ EchoServiceGrpc.EchoServiceBlockingStub stub = noCredsEchoStub.withCallCredentials(clientSecurity);
+
+ String message = req.queryParams().first("message").orElse(null);
+ Echo.EchoResponse echoResponse = stub.echo(Echo.EchoRequest.newBuilder().setMessage(message).build());
+ res.send(echoResponse.getMessage());
+ } catch (StatusRuntimeException e) {
+ res.status(GrpcHelper.toHttpResponseStatus(e)).send();
+ } catch (Throwable thrown) {
+ res.status(Http.ResponseStatus.create(500, thrown.getMessage())).send();
+ }
+}
+----
+
+[source,java]
+.Example calling a secure web endpoint from inside a gRPC service method handler
+----
+// Obtain the SecurityContext from the gRPC call Context
+SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get();
+
+// Use the SecurityContext as normal to make a http request
+Response webResponse = client.target(url)
+ .path("/test")
+ .request()
+ .property(ClientSecurityFeature.PROPERTY_CONTEXT, securityContext)
+ .get();
+----
diff --git a/docs/src/main/docs/grpc/09_marshalling.adoc b/docs/src/main/docs/grpc/09_marshalling.adoc
new file mode 100644
index 00000000000..723d200b143
--- /dev/null
+++ b/docs/src/main/docs/grpc/09_marshalling.adoc
@@ -0,0 +1,36 @@
+///////////////////////////////////////////////////////////////////////////////
+
+ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+///////////////////////////////////////////////////////////////////////////////
+
+:pagename: grpc-server-metrics
+:description: Helidon gRPC Marshalling
+:keywords: helidon, grpc, java
+
+= Marshalling
+
+== Default Marshalling Support
+
+=== Protobuf Marshalling
+
+=== Java Serialization Marshalling
+
+== Custom Marshalling
+
+=== Marshaller
+
+=== Marshaller Supplier
+
diff --git a/docs/src/main/docs/microprofile/01_introduction.adoc b/docs/src/main/docs/microprofile/01_introduction.adoc
index 9891f01a743..ad5fc34c022 100644
--- a/docs/src/main/docs/microprofile/01_introduction.adoc
+++ b/docs/src/main/docs/microprofile/01_introduction.adoc
@@ -16,23 +16,35 @@
///////////////////////////////////////////////////////////////////////////////
-= Microprofile Introduction
-:description: Helidon Microprofile introduction
+= MicroProfile Introduction
+:description: Helidon MicroProfile introduction
:keywords: helidon, microprofile, micro-profile
-MicroProfile is a platform definition that is familiar to Java EE developers. If you have experience with JAX-RS, JSON-P, and CDI, you
-may prefer to use this model.
-
-To extend the functionality of your MicroProfile application, you might also decide to use the Helidon core APIs, especially for
-configuration and security.
+MicroProfile is a collection of enterprise Java APIs that should feel familiar to
+Java EE developers. MicroProfile includes existing APIs such as JAX-RS, JSON-P and
+CDI, and adds additional APIs in areas such as configuration, metrics, fault
+tolerance and more.
== Getting Started with Helidon MicroProfile
+Helidon MP {helidon-version} supports
+MicroProfile {mp-version}. You can find the exact version of APIs supported on the
+https://github.com/oracle/helidon/wiki/Supported-APIs[Helidon Supported APIs]
+wiki page.
+
+Helidon provides a MicroProfile server implementation (`io.helidon.microprofile.server`) that
+encapsulates the Helidon WebServer. You can either instantiate the server directly
+as is done in the
+<>
+or use its built-in `main` as shown below.
+
Complete these tasks to get started with your MicroProfile application.
=== Maven Coordinates
-Declare the following dependency in your project:
+The <> page describes
+how you should declare dependency management for Helidon applications.
+Then declare the following dependency in your project:
[source,xml]
.Maven Dependency
@@ -102,7 +114,7 @@ Run the main class. The server will start on port 7001 and serve your
=== Adding Jandex
-Jandex is an indexing tool for Weld (CDI implementation) that helps speed up
+Jandex is an indexing tool for Weld (the CDI implementation used by Helidon) that helps speed up
the boot time of an application.
To use Jandex, configure a Maven plugin that adds the index to your
diff --git a/docs/src/main/docs/microprofile/04_static-content.adoc b/docs/src/main/docs/microprofile/04_static-content.adoc
index df2fb1a2ac0..1dcca1a4d6e 100644
--- a/docs/src/main/docs/microprofile/04_static-content.adoc
+++ b/docs/src/main/docs/microprofile/04_static-content.adoc
@@ -1,6 +1,6 @@
///////////////////////////////////////////////////////////////////////////////
- Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
///////////////////////////////////////////////////////////////////////////////
= Serving Static Content
-:description: Helidon Microprofile static content
+:description: Helidon MicroProfile static content
:keywords: helidon, microprofile, micro-profile
You can serve static content from a location in a file system
@@ -31,9 +31,9 @@ You can serve static content from a location in a file system
# Location of content on file system
server.static.path.location=/var/www/html
# default is index.html
-server.static.classpath.welcome=resource.html
+server.static.path.welcome=resource.html
# static content path - default is "/"
-# server.static.classpath.context=/static-file
+# server.static.path.context=/static-file
----
[source,properties]
diff --git a/docs/src/main/docs/microprofile/07_tracing.adoc b/docs/src/main/docs/microprofile/07_tracing.adoc
index 1a041faea2c..ed2e60afdb6 100644
--- a/docs/src/main/docs/microprofile/07_tracing.adoc
+++ b/docs/src/main/docs/microprofile/07_tracing.adoc
@@ -49,7 +49,7 @@ tracing.service=helidon-mp
For additional supported properties, please see <>
== Creating custom spans
-Microprofile OpenTracing implementation will add support to simply
+MicroProfile OpenTracing implementation will add support to simply
add custom spans by annotation. Until we implement this support, you
can configure custom spans as follows (in JAX-RS resources):
diff --git a/docs/src/main/docs/sitegen.yaml b/docs/src/main/docs/sitegen.yaml
index 87b7dc2f2a0..8ccff23f7d5 100644
--- a/docs/src/main/docs/sitegen.yaml
+++ b/docs/src/main/docs/sitegen.yaml
@@ -23,6 +23,7 @@ engine:
plantumlconfig: "_plantuml-config.txt"
javadoc-base-url: "./apidocs/index.html"
helidon-version: "${project.version}"
+ mp-version: "1.2"
guides-dir: "${project.basedir}/../examples/guides"
assets:
- target: "/"
@@ -69,6 +70,14 @@ backend:
items:
- includes:
- "webserver/*.adoc"
+ - title: "gRPC server"
+ pathprefix: "/grpc"
+ glyph:
+ type: "icon"
+ value: "swap_horiz"
+ items:
+ - includes:
+ - "grpc/*.adoc"
- title: "Config"
pathprefix: "/config"
glyph:
@@ -85,7 +94,7 @@ backend:
items:
- includes:
- "security/*.adoc"
- - title: "Microprofile"
+ - title: "MicroProfile"
pathprefix: "/microprofile"
glyph:
type: "icon"
diff --git a/docs/src/main/docs/tracing/01_tracing.adoc b/docs/src/main/docs/tracing/01_tracing.adoc
index 65cb1325c56..78c55e7faac 100644
--- a/docs/src/main/docs/tracing/01_tracing.adoc
+++ b/docs/src/main/docs/tracing/01_tracing.adoc
@@ -22,7 +22,7 @@
== Tracing Support
Helidon includes support for tracing through the `https://opentracing.io/[OpenTracing]` APIs.
-Tracing is integrated with WebServer and Security.
+Tracing is integrated with WebServer, gRPC Server, and Security.
Support for specific tracers is abstracted. Your application can depend on
the abstraction layer and provide a specific tracer implementation as a Java
@@ -42,6 +42,8 @@ Declare the following dependency in your project to use the tracer abstraction:
----
+=== Configuring Tracing with WebServer
+
To configure tracer with WebServer:
[source,java]
@@ -56,6 +58,40 @@ ServerConfiguration.builder()
<1> The name of the application (service) to associate with the tracing events
<2> The endpoint for tracing events, specific to the tracer used, usually loaded from Config
+=== Configuring Tracing with gRPC Server
+
+[source,java]
+
+.Configuring OpenTracing `Tracer`
+----
+Tracer tracer = (Tracer) TracerBuilder.create("Server")
+ .collectorUri(URI.create("http://10.0.0.18:9411")) // <1>
+ .build();
+----
+<3> If using zipkin tracing system, the endpoint would be:
+----
+http://10.0.0.18:9411/api/v2/spans
+----
+
+.Configuring Tracing Attributes
+----
+TracingConfiguration tracingConfig = new TracingConfiguration.Builder()
+ .withStreaming()
+ .withVerbosity()
+ .withTracedAttributes(ServerRequestAttribute.CALL_ATTRIBUTES,
+ ServerRequestAttribute.HEADERS,
+ ServerRequestAttribute.METHOD_NAME)
+ .build();
+----
+
+.Configuring gRPC Server
+----
+GrpcServerConfiguration serverConfig = GrpcServerConfiguration.builder().port(0)
+ .tracer(tracer)
+ .tracingConfig(tracingConfig)
+ .build();
+----
+
=== Configuration using Helidon Config [[Tracing-config]]
There is a set of common configuration options that this section describes. In addition each tracer implementation
may have additional configuration options - please see the documentation of each of them.
diff --git a/docs/src/main/docs/webserver/01_introduction.adoc b/docs/src/main/docs/webserver/01_introduction.adoc
index 679c1f90d2b..66f6d778422 100644
--- a/docs/src/main/docs/webserver/01_introduction.adoc
+++ b/docs/src/main/docs/webserver/01_introduction.adoc
@@ -50,7 +50,7 @@ Here is the code for a minimalist web application that runs on a random free por
== Maven Coordinates
-The <> page describes how you
+The <> page describes how you
should declare dependency management for Helidon applications. Then declare the following dependency in your project:
[source,xml,subs="verbatim,attributes"]
diff --git a/examples/grpc/README.md b/examples/grpc/README.md
new file mode 100644
index 00000000000..879c1c7d6d2
--- /dev/null
+++ b/examples/grpc/README.md
@@ -0,0 +1,4 @@
+
+# Helidon SE gRPC Server Examples
+
+
diff --git a/examples/grpc/basics/README.md b/examples/grpc/basics/README.md
new file mode 100644
index 00000000000..ef8ae7ee989
--- /dev/null
+++ b/examples/grpc/basics/README.md
@@ -0,0 +1,16 @@
+
+# Helidon gRPC Example
+
+A basic example gRPC server.
+
+## Build
+
+```
+mvn package
+```
+
+## Run
+
+```
+mvn exec:java
+```
diff --git a/examples/grpc/basics/pom.xml b/examples/grpc/basics/pom.xml
new file mode 100644
index 00000000000..fb45299efb1
--- /dev/null
+++ b/examples/grpc/basics/pom.xml
@@ -0,0 +1,67 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-project
+ 1.0.4-SNAPSHOT
+
+ helidon-examples-grpc-basics
+ Helidon gRPC Server Examples Basics
+
+
+ Examples of elementary use of the gRPC Server
+
+
+
+ io.helidon.grpc.examples.basics.Server
+
+
+
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-common
+ ${project.version}
+
+
+ io.helidon.grpc
+ helidon-grpc-server
+ ${project.version}
+
+
+
+ io.helidon.grpc
+ helidon-grpc-client
+ ${project.version}
+
+
+
+ io.helidon.health
+ helidon-health-checks
+ ${project.version}
+
+
+ io.helidon.bundles
+ helidon-bundles-config
+ ${project.version}
+
+
+
diff --git a/examples/grpc/basics/src/main/java/io/helidon/grpc/examples/basics/HealthClient.java b/examples/grpc/basics/src/main/java/io/helidon/grpc/examples/basics/HealthClient.java
new file mode 100644
index 00000000000..27a90f814f1
--- /dev/null
+++ b/examples/grpc/basics/src/main/java/io/helidon/grpc/examples/basics/HealthClient.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.basics;
+
+import io.grpc.Channel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.health.v1.HealthCheckRequest;
+import io.grpc.health.v1.HealthGrpc;
+
+/**
+ * A simple gRPC health check client.
+ */
+public class HealthClient {
+
+ private HealthClient() {
+ }
+
+ /**
+ * The program entry point.
+ *
+ * @param args the program arguments
+ *
+ * @throws Exception if an error occurs
+ */
+ public static void main(String[] args) {
+ Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408).usePlaintext().build();
+
+ HealthGrpc.HealthBlockingStub health = HealthGrpc.newBlockingStub(channel);
+ System.out.println(health.check(HealthCheckRequest.newBuilder().setService("GreetService").build()));
+ System.out.println(health.check(HealthCheckRequest.newBuilder().setService("FooService").build()));
+ }
+}
diff --git a/examples/grpc/basics/src/main/java/io/helidon/grpc/examples/basics/Server.java b/examples/grpc/basics/src/main/java/io/helidon/grpc/examples/basics/Server.java
new file mode 100644
index 00000000000..9d8f88302f9
--- /dev/null
+++ b/examples/grpc/basics/src/main/java/io/helidon/grpc/examples/basics/Server.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.basics;
+
+import java.util.logging.LogManager;
+
+import io.helidon.config.Config;
+import io.helidon.grpc.examples.common.GreetService;
+import io.helidon.grpc.examples.common.GreetServiceJava;
+import io.helidon.grpc.examples.common.StringService;
+import io.helidon.grpc.server.GrpcRouting;
+import io.helidon.grpc.server.GrpcServer;
+import io.helidon.grpc.server.GrpcServerConfiguration;
+import io.helidon.health.HealthSupport;
+import io.helidon.health.checks.HealthChecks;
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.ServerConfiguration;
+import io.helidon.webserver.WebServer;
+
+/**
+ * A basic example of a Helidon gRPC server.
+ */
+public class Server {
+
+ private Server() {
+ }
+
+ /**
+ * The main program entry point.
+ *
+ * @param args the program arguments
+ *
+ * @throws Exception if an error occurs
+ */
+ public static void main(String[] args) throws Exception {
+ // By default this will pick up application.yaml from the classpath
+ Config config = Config.create();
+
+ // load logging configuration
+ LogManager.getLogManager().readConfiguration(
+ Server.class.getResourceAsStream("/logging.properties"));
+
+ // Get gRPC server config from the "grpc" section of application.yaml
+ GrpcServerConfiguration serverConfig =
+ GrpcServerConfiguration.builder(config.get("grpc")).build();
+
+ GrpcServer grpcServer = GrpcServer.create(serverConfig, createRouting(config));
+
+ // Try to start the server. If successful, print some info and arrange to
+ // print a message at shutdown. If unsuccessful, print the exception.
+ grpcServer.start()
+ .thenAccept(s -> {
+ System.out.println("gRPC server is UP! http://localhost:" + s.port());
+ s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
+ })
+ .exceptionally(t -> {
+ System.err.println("Startup failed: " + t.getMessage());
+ t.printStackTrace(System.err);
+ return null;
+ });
+
+ // add support for standard and gRPC health checks
+ HealthSupport health = HealthSupport.builder()
+ .add(HealthChecks.healthChecks())
+ .add(grpcServer.healthChecks())
+ .build();
+
+ // start web server with health endpoint
+ Routing routing = Routing.builder()
+ .register(health)
+ .build();
+
+ ServerConfiguration webServerConfig = ServerConfiguration.builder(config.get("webserver")).build();
+
+ WebServer.create(webServerConfig, routing)
+ .start()
+ .thenAccept(s -> {
+ System.out.println("HTTP server is UP! http://localhost:" + s.port());
+ s.whenShutdown().thenRun(() -> System.out.println("HTTP server is DOWN. Good bye!"));
+ })
+ .exceptionally(t -> {
+ System.err.println("Startup failed: " + t.getMessage());
+ t.printStackTrace(System.err);
+ return null;
+ });
+ }
+
+ private static GrpcRouting createRouting(Config config) {
+ GreetService greetService = new GreetService(config);
+ GreetServiceJava greetServiceJava = new GreetServiceJava(config);
+
+ return GrpcRouting.builder()
+ .register(greetService)
+ .register(greetServiceJava)
+ .register(new StringService())
+ .build();
+ }
+}
diff --git a/examples/grpc/basics/src/main/java/io/helidon/grpc/examples/basics/package-info.java b/examples/grpc/basics/src/main/java/io/helidon/grpc/examples/basics/package-info.java
new file mode 100644
index 00000000000..fa903037233
--- /dev/null
+++ b/examples/grpc/basics/src/main/java/io/helidon/grpc/examples/basics/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A set of small usage examples. Start with {@link io.helidon.grpc.examples.basics.Server Main} class.
+ */
+package io.helidon.grpc.examples.basics;
diff --git a/examples/grpc/basics/src/main/resources/application.yaml b/examples/grpc/basics/src/main/resources/application.yaml
new file mode 100644
index 00000000000..2afc282df4a
--- /dev/null
+++ b/examples/grpc/basics/src/main/resources/application.yaml
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+app:
+ greeting: "Hello"
+
+grpc:
+ name: "test.server"
+ port: 1408
+
+webserver:
+ port: 8080
+ bind-address: "0.0.0.0"
\ No newline at end of file
diff --git a/examples/grpc/basics/src/main/resources/logging.properties b/examples/grpc/basics/src/main/resources/logging.properties
new file mode 100644
index 00000000000..f903114790c
--- /dev/null
+++ b/examples/grpc/basics/src/main/resources/logging.properties
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=java.util.logging.ConsoleHandler
+
+# Global default logging level. Can be overriden by specific handlers and loggers
+.level=INFO
+
+# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
+# It replaces "!thread!" with the current thread name
+java.util.logging.ConsoleHandler.level=INFO
+java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+#Component specific log levels
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
+#io.netty.level=INFO
diff --git a/examples/grpc/common/pom.xml b/examples/grpc/common/pom.xml
new file mode 100644
index 00000000000..da988014671
--- /dev/null
+++ b/examples/grpc/common/pom.xml
@@ -0,0 +1,98 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-project
+ 1.0.4-SNAPSHOT
+
+ helidon-examples-grpc-common
+ Helidon gRPC Server Examples ProtoBuf Services
+
+
+ ProtoBuf generated gRPC services used in gRPC examples
+
+
+
+ io.helidon.grpc.examples.common.GreetClient
+
+
+
+
+ io.helidon.grpc
+ helidon-grpc-server
+ ${project.version}
+
+
+
+ io.helidon.grpc
+ helidon-grpc-client
+ ${project.version}
+
+
+
+ io.grpc
+ grpc-netty
+
+
+ io.grpc
+ grpc-stub
+
+
+ io.grpc
+ grpc-services
+
+
+ io.grpc
+ grpc-protobuf
+
+
+
+ io.helidon.common
+ helidon-common
+ ${project.version}
+
+
+
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ ${version.plugin.os}
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+
+
+
+ compile
+ compile-custom
+
+
+
+
+
+
+
diff --git a/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/GreetClient.java b/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/GreetClient.java
new file mode 100644
index 00000000000..817136d247a
--- /dev/null
+++ b/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/GreetClient.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.common;
+
+import java.net.URI;
+
+import io.helidon.grpc.client.ClientRequestAttribute;
+import io.helidon.grpc.client.ClientTracingInterceptor;
+import io.helidon.grpc.examples.common.Greet.GreetRequest;
+import io.helidon.grpc.examples.common.Greet.SetGreetingRequest;
+import io.helidon.tracing.TracerBuilder;
+
+import io.grpc.Channel;
+import io.grpc.ClientInterceptors;
+import io.grpc.ManagedChannelBuilder;
+import io.opentracing.Tracer;
+
+/**
+ * A client for the {@link GreetService}.
+ */
+public class GreetClient {
+
+ private GreetClient() {
+ }
+
+ /**
+ * The program entry point.
+ * @param args the program arguments
+ *
+ * @throws Exception if an error occurs
+ */
+ public static void main(String[] args) throws Exception {
+ Tracer tracer = (Tracer) TracerBuilder.create("Client")
+ .collectorUri(URI.create("http://localhost:9411/api/v2/spans"))
+ .build();
+
+ ClientTracingInterceptor tracingInterceptor = ClientTracingInterceptor.builder(tracer)
+ .withVerbosity().withTracedAttributes(ClientRequestAttribute.ALL_CALL_OPTIONS).build();
+
+ Channel channel = ClientInterceptors
+ .intercept(ManagedChannelBuilder.forAddress("localhost", 1408).usePlaintext().build(), tracingInterceptor);
+
+ GreetServiceGrpc.GreetServiceBlockingStub greetSvc = GreetServiceGrpc.newBlockingStub(channel);
+ System.out.println(greetSvc.greet(GreetRequest.newBuilder().setName("Aleks").build()));
+ System.out.println(greetSvc.setGreeting(SetGreetingRequest.newBuilder().setGreeting("Ciao").build()));
+ System.out.println(greetSvc.greet(GreetRequest.newBuilder().setName("Aleks").build()));
+
+ Thread.sleep(5000);
+ }
+}
diff --git a/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/GreetService.java b/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/GreetService.java
new file mode 100644
index 00000000000..2e801895c69
--- /dev/null
+++ b/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/GreetService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.common;
+
+import java.util.Optional;
+
+import io.helidon.config.Config;
+import io.helidon.grpc.examples.common.Greet.GreetRequest;
+import io.helidon.grpc.examples.common.Greet.GreetResponse;
+import io.helidon.grpc.examples.common.Greet.SetGreetingRequest;
+import io.helidon.grpc.examples.common.Greet.SetGreetingResponse;
+import io.helidon.grpc.server.GrpcService;
+import io.helidon.grpc.server.ServiceDescriptor;
+
+import io.grpc.stub.StreamObserver;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+
+/**
+ * An implementation of the GreetService.
+ */
+public class GreetService implements GrpcService {
+ /**
+ * The config value for the key {@code greeting}.
+ */
+ private String greeting;
+
+ /**
+ * Create a {@link GreetService}.
+ *
+ * @param config the service configuration
+ */
+ public GreetService(Config config) {
+ this.greeting = config.get("app.greeting").asString().orElse("Ciao");
+ }
+
+ @Override
+ public void update(ServiceDescriptor.Rules rules) {
+ rules.proto(Greet.getDescriptor())
+ .unary("Greet", this::greet)
+ .unary("SetGreeting", this::setGreeting)
+ .healthCheck(this::healthCheck);
+ }
+
+ // ---- service methods -------------------------------------------------
+
+ private void greet(GreetRequest request, StreamObserver observer) {
+ String name = Optional.ofNullable(request.getName()).orElse("World");
+ String msg = String.format("%s %s!", greeting, name);
+
+ complete(observer, GreetResponse.newBuilder().setMessage(msg).build());
+ }
+
+ private void setGreeting(SetGreetingRequest request, StreamObserver observer) {
+ greeting = request.getGreeting();
+
+ complete(observer, SetGreetingResponse.newBuilder().setGreeting(greeting).build());
+ }
+
+ private HealthCheckResponse healthCheck() {
+ return HealthCheckResponse
+ .named(name())
+ .up()
+ .withData("time", System.currentTimeMillis())
+ .withData("greeting", greeting)
+ .build();
+ }
+}
diff --git a/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/GreetServiceJava.java b/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/GreetServiceJava.java
new file mode 100644
index 00000000000..b03cb7e7916
--- /dev/null
+++ b/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/GreetServiceJava.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.common;
+
+import java.util.Optional;
+
+import io.helidon.config.Config;
+import io.helidon.grpc.server.GrpcService;
+import io.helidon.grpc.server.ServiceDescriptor;
+
+import io.grpc.stub.StreamObserver;
+
+/**
+ * A plain Java implementation of the GreetService.
+ */
+public class GreetServiceJava
+ implements GrpcService {
+ /**
+ * The config value for the key {@code greeting}.
+ */
+ private String greeting;
+
+ /**
+ * Create a {@link GreetServiceJava}.
+ *
+ * @param config the service configuration
+ */
+ public GreetServiceJava(Config config) {
+ this.greeting = config.get("app.greeting").asString().orElse("Ciao");
+ }
+
+ @Override
+ public void update(ServiceDescriptor.Rules rules) {
+ rules.unary("Greet", this::greet)
+ .unary("SetGreeting", this::setGreeting);
+ }
+
+ // ---- service methods -------------------------------------------------
+
+ private void greet(String name, StreamObserver observer) {
+ name = Optional.ofNullable(name).orElse("World");
+ String msg = String.format("%s %s!", greeting, name);
+
+ complete(observer, msg);
+ }
+
+ private void setGreeting(String greeting, StreamObserver observer) {
+ this.greeting = greeting;
+
+ complete(observer, greeting);
+ }
+}
diff --git a/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/StringClient.java b/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/StringClient.java
new file mode 100644
index 00000000000..84f812ff34f
--- /dev/null
+++ b/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/StringClient.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.common;
+
+import io.helidon.grpc.examples.common.Strings.StringMessage;
+
+import io.grpc.Channel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.stub.StreamObserver;
+
+/**
+ * A client to the {@link io.helidon.grpc.examples.common.StringService}.
+ */
+public class StringClient {
+
+ private StringClient() {
+ }
+
+ /**
+ * Program entry point.
+ *
+ * @param args the program arguments
+ *
+ * @throws Exception if an error occurs
+ */
+ public static void main(String[] args) throws Exception {
+ Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408).usePlaintext().build();
+
+ StringServiceGrpc.StringServiceStub stub = StringServiceGrpc.newStub(channel);
+ stub.lower(stringMessage("Convert To Lowercase"), new PrintObserver<>());
+ Thread.sleep(500L);
+ stub.upper(stringMessage("Convert to Uppercase"), new PrintObserver<>());
+ Thread.sleep(500L);
+ stub.split(stringMessage("Let's split some text"), new PrintObserver<>());
+ Thread.sleep(500L);
+
+ StreamObserver sender = stub.join(new PrintObserver<>());
+ sender.onNext(stringMessage("Let's"));
+ sender.onNext(stringMessage("join"));
+ sender.onNext(stringMessage("some"));
+ sender.onNext(stringMessage("text"));
+ sender.onCompleted();
+ Thread.sleep(500L);
+
+ sender = stub.echo(new PrintObserver<>());
+ sender.onNext(stringMessage("Let's"));
+ sender.onNext(stringMessage("echo"));
+ sender.onNext(stringMessage("some"));
+ sender.onNext(stringMessage("text"));
+ sender.onCompleted();
+ Thread.sleep(500L);
+ }
+
+ private static StringMessage stringMessage(String text) {
+ return StringMessage.newBuilder().setText(text).build();
+ }
+
+ static class PrintObserver implements StreamObserver {
+ public void onNext(T value) {
+ System.out.println(value);
+ }
+
+ public void onError(Throwable t) {
+ t.printStackTrace();
+ }
+
+ public void onCompleted() {
+ System.out.println("");
+ }
+ }
+}
diff --git a/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/StringService.java b/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/StringService.java
new file mode 100644
index 00000000000..8a409c571fa
--- /dev/null
+++ b/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/StringService.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.common;
+
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import io.helidon.grpc.examples.common.Strings.StringMessage;
+import io.helidon.grpc.server.CollectingObserver;
+import io.helidon.grpc.server.GrpcService;
+import io.helidon.grpc.server.ServiceDescriptor;
+
+import io.grpc.stub.StreamObserver;
+
+/**
+ * AN implementation of the StringService.
+ */
+public class StringService
+ implements GrpcService {
+ @Override
+ public void update(ServiceDescriptor.Rules rules) {
+ rules.proto(Strings.getDescriptor())
+ .unary("Upper", this::upper)
+ .unary("Lower", this::lower)
+ .serverStreaming("Split", this::split)
+ .clientStreaming("Join", this::join)
+ .bidirectional("Echo", this::echo);
+ }
+
+ // ---- service methods -------------------------------------------------
+
+ private void upper(StringMessage request, StreamObserver observer) {
+ complete(observer, response(request.getText().toUpperCase()));
+ }
+
+ private void lower(StringMessage request, StreamObserver observer) {
+ complete(observer, response(request.getText().toLowerCase()));
+ }
+
+ private void split(StringMessage request, StreamObserver observer) {
+ String[] parts = request.getText().split(" ");
+ stream(observer, Stream.of(parts).map(this::response));
+ }
+
+ private StreamObserver join(StreamObserver observer) {
+ return new CollectingObserver<>(
+ Collectors.joining(" "),
+ observer,
+ StringMessage::getText,
+ this::response);
+ }
+
+ private StreamObserver echo(StreamObserver observer) {
+ return new StreamObserver() {
+ public void onNext(StringMessage value) {
+ observer.onNext(value);
+ }
+
+ public void onError(Throwable t) {
+ t.printStackTrace();
+ }
+
+ public void onCompleted() {
+ observer.onCompleted();
+ }
+ };
+ }
+
+ // ---- helper methods --------------------------------------------------
+
+ private StringMessage response(String text) {
+ return StringMessage.newBuilder().setText(text).build();
+ }
+
+}
diff --git a/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/package-info.java b/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/package-info.java
new file mode 100644
index 00000000000..777b05b9600
--- /dev/null
+++ b/examples/grpc/common/src/main/java/io/helidon/grpc/examples/common/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Common classes and ProtoBuf generated gRPC servcies used in the Helidon gROC examples.
+ */
+package io.helidon.grpc.examples.common;
diff --git a/examples/grpc/common/src/main/proto/greet.proto b/examples/grpc/common/src/main/proto/greet.proto
new file mode 100644
index 00000000000..12d21aedd6a
--- /dev/null
+++ b/examples/grpc/common/src/main/proto/greet.proto
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+syntax = "proto3";
+option java_package = "io.helidon.grpc.examples.common";
+
+service GreetService {
+ rpc Greet (GreetRequest) returns (GreetResponse) {}
+ rpc SetGreeting (SetGreetingRequest) returns (SetGreetingResponse) {}
+}
+
+message GreetRequest {
+ string name = 1;
+}
+
+message GreetResponse {
+ string message = 1;
+}
+
+message SetGreetingRequest {
+ string greeting = 1;
+}
+
+message SetGreetingResponse {
+ string greeting = 1;
+}
diff --git a/examples/grpc/common/src/main/proto/strings.proto b/examples/grpc/common/src/main/proto/strings.proto
new file mode 100644
index 00000000000..aeb5800ddef
--- /dev/null
+++ b/examples/grpc/common/src/main/proto/strings.proto
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+syntax = "proto3";
+option java_package = "io.helidon.grpc.examples.common";
+
+service StringService {
+ rpc Upper (StringMessage) returns (StringMessage) {}
+ rpc Lower (StringMessage) returns (StringMessage) {}
+ rpc Split (StringMessage) returns (stream StringMessage) {}
+ rpc Join (stream StringMessage) returns (StringMessage) {}
+ rpc Echo (stream StringMessage) returns (stream StringMessage) {}
+}
+
+message StringMessage {
+ string text = 1;
+}
diff --git a/examples/grpc/metrics/pom.xml b/examples/grpc/metrics/pom.xml
new file mode 100644
index 00000000000..f91288caa4d
--- /dev/null
+++ b/examples/grpc/metrics/pom.xml
@@ -0,0 +1,66 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-project
+ 1.0.4-SNAPSHOT
+
+ helidon-examples-grpc-metrics
+ Helidon gRPC Server Examples Metrics
+
+
+ Examples of elementary use of the gRPC Server metrics
+
+
+
+ io.helidon.grpc.examples.metrics.Server
+
+
+
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-common
+ ${project.version}
+
+
+ io.helidon.grpc
+ helidon-grpc-server
+ ${project.version}
+
+
+ io.helidon.grpc
+ helidon-grpc-metrics
+ ${project.version}
+
+
+ io.helidon.bundles
+ helidon-bundles-config
+ ${project.version}
+
+
+
+ io.helidon.grpc
+ helidon-grpc-client
+ ${project.version}
+
+
+
diff --git a/examples/grpc/metrics/src/main/java/io/helidon/grpc/examples/metrics/Server.java b/examples/grpc/metrics/src/main/java/io/helidon/grpc/examples/metrics/Server.java
new file mode 100644
index 00000000000..bec8ffde1cf
--- /dev/null
+++ b/examples/grpc/metrics/src/main/java/io/helidon/grpc/examples/metrics/Server.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.metrics;
+
+import java.util.logging.LogManager;
+
+import io.helidon.config.Config;
+import io.helidon.grpc.examples.common.GreetService;
+import io.helidon.grpc.examples.common.StringService;
+import io.helidon.grpc.metrics.GrpcMetrics;
+import io.helidon.grpc.server.GrpcRouting;
+import io.helidon.grpc.server.GrpcServer;
+import io.helidon.grpc.server.GrpcServerConfiguration;
+import io.helidon.metrics.MetricsSupport;
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.ServerConfiguration;
+import io.helidon.webserver.WebServer;
+
+/**
+ * A basic example of a Helidon gRPC server.
+ */
+public class Server {
+
+ private Server() {
+ }
+
+ /**
+ * The main program entry point.
+ *
+ * @param args the program arguments
+ *
+ * @throws Exception if an error occurs
+ */
+ public static void main(String[] args) throws Exception {
+ // By default this will pick up application.yaml from the classpath
+ Config config = Config.create();
+
+ // load logging configuration
+ LogManager.getLogManager().readConfiguration(
+ Server.class.getResourceAsStream("/logging.properties"));
+
+ // Get gRPC server config from the "grpc" section of application.yaml
+ GrpcServerConfiguration serverConfig =
+ GrpcServerConfiguration.builder(config.get("grpc")).build();
+
+ GrpcRouting grpcRouting = GrpcRouting.builder()
+ .intercept(GrpcMetrics.counted()) // global metrics - all service methods counted
+ .register(new GreetService(config)) // GreetService uses global metrics so all methods are counted
+ .register(new StringService(), rules -> {
+ // service level metrics - StringService overrides global so that its methods are timed
+ rules.intercept(GrpcMetrics.timed())
+ // method level metrics - overrides service and global
+ .intercept("Upper", GrpcMetrics.histogram());
+ })
+ .build();
+
+ GrpcServer grpcServer = GrpcServer.create(serverConfig, grpcRouting);
+
+ // Try to start the server. If successful, print some info and arrange to
+ // print a message at shutdown. If unsuccessful, print the exception.
+ grpcServer.start()
+ .thenAccept(s -> {
+ System.out.println("gRPC server is UP! http://localhost:" + s.port());
+ s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
+ })
+ .exceptionally(t -> {
+ System.err.println("Startup failed: " + t.getMessage());
+ t.printStackTrace(System.err);
+ return null;
+ });
+
+ // start web server with the metrics endpoints
+ Routing routing = Routing.builder()
+ .register(MetricsSupport.create())
+ .build();
+
+ ServerConfiguration webServerConfig = ServerConfiguration.builder(config.get("webserver")).build();
+
+ WebServer.create(webServerConfig, routing)
+ .start()
+ .thenAccept(s -> {
+ System.out.println("HTTP server is UP! http://localhost:" + s.port());
+ s.whenShutdown().thenRun(() -> System.out.println("HTTP server is DOWN. Good bye!"));
+ })
+ .exceptionally(t -> {
+ System.err.println("Startup failed: " + t.getMessage());
+ t.printStackTrace(System.err);
+ return null;
+ });
+ }
+}
diff --git a/examples/grpc/metrics/src/main/java/io/helidon/grpc/examples/metrics/package-info.java b/examples/grpc/metrics/src/main/java/io/helidon/grpc/examples/metrics/package-info.java
new file mode 100644
index 00000000000..342c0dcd8a6
--- /dev/null
+++ b/examples/grpc/metrics/src/main/java/io/helidon/grpc/examples/metrics/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * An example of gRPC metrics.
+ *
+ * Start with {@link io.helidon.grpc.examples.metrics.Server Main} class.
+ */
+package io.helidon.grpc.examples.metrics;
diff --git a/examples/grpc/metrics/src/main/resources/application.yaml b/examples/grpc/metrics/src/main/resources/application.yaml
new file mode 100644
index 00000000000..2afc282df4a
--- /dev/null
+++ b/examples/grpc/metrics/src/main/resources/application.yaml
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+app:
+ greeting: "Hello"
+
+grpc:
+ name: "test.server"
+ port: 1408
+
+webserver:
+ port: 8080
+ bind-address: "0.0.0.0"
\ No newline at end of file
diff --git a/examples/grpc/metrics/src/main/resources/logging.properties b/examples/grpc/metrics/src/main/resources/logging.properties
new file mode 100644
index 00000000000..f903114790c
--- /dev/null
+++ b/examples/grpc/metrics/src/main/resources/logging.properties
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=java.util.logging.ConsoleHandler
+
+# Global default logging level. Can be overriden by specific handlers and loggers
+.level=INFO
+
+# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
+# It replaces "!thread!" with the current thread name
+java.util.logging.ConsoleHandler.level=INFO
+java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+#Component specific log levels
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
+#io.netty.level=INFO
diff --git a/examples/grpc/opentracing/README.md b/examples/grpc/opentracing/README.md
new file mode 100644
index 00000000000..24aa3947cb4
--- /dev/null
+++ b/examples/grpc/opentracing/README.md
@@ -0,0 +1,86 @@
+Opentracing gRPC Server Example Application
+===========================================
+
+Running locally
+---------------
+Prerequisites:
+1. Requirements: JDK9, Maven, Docker (optional)
+2. Add following lines to `/etc/hosts`
+ ```
+ 127.0.0.1 zipkin
+ ```
+3. Run Zipkin:
+ In Docker:
+ ```
+ docker run -d -p 9411:9411 openzipkin/zipkin
+ ```
+ or with Java 8:
+ ```
+ wget -O zipkin.jar 'https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec'
+ java -jar zipkin.jar
+ ```
+
+Build and run:
+```
+mvn clean install -pl examples/grpc/opentracing
+mvn exec:java -pl examples/grpc/opentracing
+curl "http://localhost:8080/test"
+```
+Check out the traces at: ```http://zipkin:9411```
+
+
+Running in Minikube
+-------------------
+
+### Preparing the infrastructure ###
+Starting Minikube
+
+```
+% minikube start
+
+% kubectl version
+Client Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.2", GitCommit:"477efc3cbe6a7effca06bd1452fa356e2201e1ee", GitTreeState:"clean", BuildDate:"2017-04-19T22:51:36Z", GoVersion:"go1.8.1", Compiler:"gc", Platform:"darwin/amd64"}
+Server Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.0", GitCommit:"fff5156092b56e6bd60fff75aad4dc9de6b6ef37", GitTreeState:"dirty", BuildDate:"2017-04-07T20:46:46Z", GoVersion:"go1.7.3", Compiler:"gc", Platform:"linux/amd64"}
+
+% minikube dashboard
+ Waiting, endpoint for service is not ready yet...
+ Opening kubernetes dashboard in default browser...
+
+```
+
+Running Zipkin in K8S
+```
+% kubectl run zipkin --image=openzipkin/zipkin --port=9411
+deployment "zipkin" created
+
+% kubectl expose deployment zipkin --type=NodePort
+service "zipkin" exposed
+
+% kubectl get pod
+NAME READY STATUS RESTARTS AGE
+zipkin-2596933303-bccnw 0/1 ContainerCreating 0 14s
+
+% kubectl get pod
+NAME READY STATUS RESTARTS AGE
+zipkin-2596933303-bccnw 1/1 Running 0 16s
+
+% minikube service zipkin
+Opening kubernetes service default/zipkin in default browser...
+```
+
+Running opentracing app
+```
+% eval $(minikube docker-env)
+% mvn clean install -pl examples/grpc/opentracing docker:build
+
+% kubectl run helidon-grpc-opentracing-example --image=mic.docker.oraclecorp.com/helidon-grpc-opentracing-example:1.0.1-SNAPSHOT --port=1408 --image-pull-policy=Never
+deployment "helidon-grpc-opentracing-example" created
+
+% kubectl expose deployment helidon-grpc-opentracing-example --type=NodePort
+service "helidon-grpc-opentracing-example" exposed
+
+% curl $(minikube service helidon-webserver-opentracing-example --url)/test
+Hello World!%
+```
+
+
diff --git a/examples/grpc/opentracing/pom.xml b/examples/grpc/opentracing/pom.xml
new file mode 100644
index 00000000000..171d095c149
--- /dev/null
+++ b/examples/grpc/opentracing/pom.xml
@@ -0,0 +1,123 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-project
+ 1.0.4-SNAPSHOT
+
+ helidon-examples-grpc-opentracing
+ Helidon gRPC Server Examples OpenTracing
+
+
+ Examples gRPC application using Open Tracing
+
+
+
+ io.helidon.grpc.examples.opentracing.ZipkinExampleMain
+
+ ${docker.registry}/helidon-grpc-opentracing-example:${project.version}
+
+ mic-docker-registry-automation
+ mic.docker.oraclecorp.com
+ https://${docker.registry}/v1/
+ 0.3.3
+
+
+
+
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-common
+ ${project.version}
+
+
+ io.helidon.grpc
+ helidon-grpc-server
+ ${project.version}
+
+
+ io.helidon.bundles
+ helidon-bundles-config
+ ${project.version}
+
+
+
+ io.helidon.grpc
+ helidon-grpc-client
+ ${project.version}
+
+
+
+ io.helidon.tracing
+ helidon-tracing-zipkin
+ ${project.version}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+ copy-docker-resources
+ generate-resources
+
+ copy-resources
+
+
+ ${project.build.directory}/distribution/container
+
+
+ src/main/docker
+ true
+
+
+
+
+
+
+
+ com.spotify
+ docker-maven-plugin
+
+ ${docker.server.id}
+ ${docker.registry.url}
+ ${docker.image.name}
+ ${project.build.directory}/distribution/container
+
+ ${docker.image.version}
+ latest
+
+
+
+ /
+ ${project.build.directory}
+ ${project.build.finalName}-fat.jar
+
+
+
+
+
+
+
diff --git a/examples/grpc/opentracing/src/main/docker/Dockerfile b/examples/grpc/opentracing/src/main/docker/Dockerfile
new file mode 100644
index 00000000000..87aef649fb6
--- /dev/null
+++ b/examples/grpc/opentracing/src/main/docker/Dockerfile
@@ -0,0 +1,23 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+FROM java:9
+
+EXPOSE 8080
+COPY ./* /
+RUN rm Dockerfile
+WORKDIR /
+CMD if [ ! -z "$MIC_CONTAINER_MEM_QUOTA" ]; then java -Xmx${MIC_CONTAINER_MEM_QUOTA}m ${JAVA_OPTS} -jar ${artifactId}-${version}-fat.jar ; else java ${JAVA_OPTS} -jar ${artifactId}-${version}-fat.jar ; fi
diff --git a/examples/grpc/opentracing/src/main/java/io/helidon/grpc/examples/opentracing/ZipkinExampleMain.java b/examples/grpc/opentracing/src/main/java/io/helidon/grpc/examples/opentracing/ZipkinExampleMain.java
new file mode 100644
index 00000000000..c2e0d5269c4
--- /dev/null
+++ b/examples/grpc/opentracing/src/main/java/io/helidon/grpc/examples/opentracing/ZipkinExampleMain.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.opentracing;
+
+import java.util.logging.LogManager;
+
+import io.helidon.config.Config;
+import io.helidon.grpc.examples.common.GreetService;
+import io.helidon.grpc.examples.common.StringService;
+import io.helidon.grpc.server.GrpcRouting;
+import io.helidon.grpc.server.GrpcServer;
+import io.helidon.grpc.server.GrpcServerConfiguration;
+import io.helidon.grpc.server.ServerRequestAttribute;
+import io.helidon.grpc.server.TracingConfiguration;
+import io.helidon.tracing.TracerBuilder;
+
+import io.opentracing.Tracer;
+
+/**
+ * An example gRPC server with Zipkin tracing enabled.
+ */
+public class ZipkinExampleMain {
+
+ private ZipkinExampleMain() {
+ }
+
+ /**
+ * Program entry point.
+ *
+ * @param args the program command line arguments
+ * @throws Exception if there is a program error
+ */
+ public static void main(String[] args) throws Exception {
+ // By default this will pick up application.yaml from the classpath
+ Config config = Config.create();
+
+ // load logging configuration
+ LogManager.getLogManager().readConfiguration(
+ ZipkinExampleMain.class.getResourceAsStream("/logging.properties"));
+
+ Tracer tracer = TracerBuilder.create(config.get("tracing")).build();
+
+ TracingConfiguration tracingConfig = new TracingConfiguration.Builder()
+ .withStreaming()
+ .withVerbosity()
+ .withTracedAttributes(ServerRequestAttribute.CALL_ATTRIBUTES,
+ ServerRequestAttribute.HEADERS,
+ ServerRequestAttribute.METHOD_NAME)
+ .build();
+
+ // Get gRPC server config from the "grpc" section of application.yaml
+ GrpcServerConfiguration serverConfig =
+ GrpcServerConfiguration.builder(config.get("grpc")).tracer(tracer).tracingConfig(tracingConfig).build();
+
+ GrpcServer grpcServer = GrpcServer.create(serverConfig, createRouting(config));
+
+ // Try to start the server. If successful, print some info and arrange to
+ // print a message at shutdown. If unsuccessful, print the exception.
+ grpcServer.start()
+ .thenAccept(s -> {
+ System.out.println("gRPC server is UP! http://localhost:" + s.port());
+ s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
+ })
+ .exceptionally(t -> {
+ System.err.println("Startup failed: " + t.getMessage());
+ t.printStackTrace(System.err);
+ return null;
+ });
+ }
+
+ private static GrpcRouting createRouting(Config config) {
+ return GrpcRouting.builder()
+ .register(new GreetService(config))
+ .register(new StringService())
+ .build();
+ }
+}
diff --git a/examples/grpc/opentracing/src/main/java/io/helidon/grpc/examples/opentracing/package-info.java b/examples/grpc/opentracing/src/main/java/io/helidon/grpc/examples/opentracing/package-info.java
new file mode 100644
index 00000000000..499076694fc
--- /dev/null
+++ b/examples/grpc/opentracing/src/main/java/io/helidon/grpc/examples/opentracing/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A set of small usage examples of running the Helidon gRPC server with Zipkin tracing enabled.
+ */
+package io.helidon.grpc.examples.opentracing;
diff --git a/examples/grpc/opentracing/src/main/resources/application.yaml b/examples/grpc/opentracing/src/main/resources/application.yaml
new file mode 100644
index 00000000000..633ac6e3672
--- /dev/null
+++ b/examples/grpc/opentracing/src/main/resources/application.yaml
@@ -0,0 +1,29 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+app:
+ greeting: "Hello"
+
+grpc:
+ name: "test.server"
+ port: 1408
+
+webserver:
+ port: 8080
+ bind-address: "0.0.0.0"
+
+tracing:
+ service: "grpc-server"
\ No newline at end of file
diff --git a/examples/grpc/opentracing/src/main/resources/logging.properties b/examples/grpc/opentracing/src/main/resources/logging.properties
new file mode 100644
index 00000000000..f903114790c
--- /dev/null
+++ b/examples/grpc/opentracing/src/main/resources/logging.properties
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=java.util.logging.ConsoleHandler
+
+# Global default logging level. Can be overriden by specific handlers and loggers
+.level=INFO
+
+# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
+# It replaces "!thread!" with the current thread name
+java.util.logging.ConsoleHandler.level=INFO
+java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+#Component specific log levels
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
+#io.netty.level=INFO
diff --git a/examples/grpc/pom.xml b/examples/grpc/pom.xml
new file mode 100644
index 00000000000..87d664f8490
--- /dev/null
+++ b/examples/grpc/pom.xml
@@ -0,0 +1,43 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples
+ helidon-examples-project
+ 1.0.4-SNAPSHOT
+
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-project
+ Helidon gRPC Examples
+ pom
+
+
+ common
+ basics
+ metrics
+ opentracing
+ security
+ security-abac
+ security-outbound
+
+
+
diff --git a/examples/grpc/security-abac/README.md b/examples/grpc/security-abac/README.md
new file mode 100644
index 00000000000..6c240ed4366
--- /dev/null
+++ b/examples/grpc/security-abac/README.md
@@ -0,0 +1,16 @@
+
+# Helidon gRPC Security ABAC Example
+
+An example gRPC server for attribute based access control.
+
+## Build
+
+```
+mvn package
+```
+
+## Run
+
+```
+mvn exec:java
+```
diff --git a/examples/grpc/security-abac/pom.xml b/examples/grpc/security-abac/pom.xml
new file mode 100644
index 00000000000..648ac300c48
--- /dev/null
+++ b/examples/grpc/security-abac/pom.xml
@@ -0,0 +1,103 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-project
+ 1.0.4-SNAPSHOT
+
+ helidon-examples-grpc-security-abac
+ Helidon gRPC Server Examples ABAC Security
+
+
+ Examples of securing gRPC services using ABAC
+
+
+
+ io.helidon.grpc.examples.security.abac.AbacServer
+
+
+
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-common
+ ${project.version}
+
+
+
+ io.helidon.grpc
+ helidon-grpc-core
+ ${project.version}
+
+
+ io.helidon.grpc
+ helidon-grpc-server
+ ${project.version}
+
+
+ io.helidon.security.integration
+ helidon-security-integration-grpc
+ ${project.version}
+
+
+ io.helidon.bundles
+ helidon-bundles-config
+ ${project.version}
+
+
+ io.helidon.bundles
+ helidon-bundles-security
+ ${project.version}
+
+
+ io.helidon.security.abac
+ helidon-security-abac-policy-el
+ ${project.version}
+
+
+ org.glassfish
+ javax.el
+
+
+
+ io.helidon.grpc
+ helidon-grpc-client
+ ${project.version}
+
+
+
+ io.grpc
+ grpc-netty
+
+
+ io.grpc
+ grpc-stub
+
+
+ io.grpc
+ grpc-services
+
+
+ io.grpc
+ grpc-protobuf
+
+
+
diff --git a/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AbacServer.java b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AbacServer.java
new file mode 100644
index 00000000000..59164b3af1f
--- /dev/null
+++ b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AbacServer.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.security.abac;
+
+import java.time.DayOfWeek;
+import java.time.LocalTime;
+import java.util.logging.LogManager;
+
+import io.helidon.grpc.examples.common.StringService;
+import io.helidon.grpc.server.GrpcRouting;
+import io.helidon.grpc.server.GrpcServer;
+import io.helidon.grpc.server.GrpcServerConfiguration;
+import io.helidon.grpc.server.ServiceDescriptor;
+import io.helidon.security.Security;
+import io.helidon.security.SubjectType;
+import io.helidon.security.abac.policy.PolicyValidator;
+import io.helidon.security.abac.scope.ScopeValidator;
+import io.helidon.security.abac.time.TimeValidator;
+import io.helidon.security.integration.grpc.GrpcSecurity;
+import io.helidon.security.providers.abac.AbacProvider;
+
+/**
+ * An example of a secure gRPC server that uses
+ * ABAC security configured in the code below.
+ *
+ * This server configures in code the same rules that
+ * the {@link AbacServerFromConfig} class uses from
+ * its configuration.
+ */
+public class AbacServer {
+
+ private AbacServer() {
+ }
+
+ /**
+ * Main entry point.
+ *
+ * @param args the program arguments
+ *
+ * @throws Exception if an error occurs
+ */
+ public static void main(String[] args) throws Exception {
+ LogManager.getLogManager().readConfiguration(
+ AbacServer.class.getResourceAsStream("/logging.properties"));
+
+ Security security = Security.builder()
+ .addProvider(AtnProvider.builder().build()) // add out custom provider
+ .addProvider(AbacProvider.builder().build()) // add the ABAC provider
+ .build();
+
+ // Create the time validator that will be used by the ABAC security provider
+ TimeValidator.TimeConfig validTimes = TimeValidator.TimeConfig.builder()
+ .addBetween(LocalTime.of(8, 15), LocalTime.of(12, 0))
+ .addBetween(LocalTime.of(12, 30), LocalTime.of(17, 30))
+ .addDaysOfWeek(DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY, DayOfWeek.FRIDAY)
+ .build();
+
+ // Create the policy validator that will be used by the ABAC security provider
+ PolicyValidator.PolicyConfig validPolicy = PolicyValidator.PolicyConfig.builder()
+ .statement("${env.time.year >= 2017}")
+ .build();
+
+ // Create the scope validator that will be used by the ABAC security provider
+ ScopeValidator.ScopesConfig validScopes = ScopeValidator.ScopesConfig.create("calendar_read", "calendar_edit");
+
+ // Create the Atn config that will be used by out custom security provider
+ AtnProvider.AtnConfig atnConfig = AtnProvider.AtnConfig.builder()
+ .addAuth(AtnProvider.Auth.builder("user")
+ .type(SubjectType.USER)
+ .roles("user_role")
+ .scopes("calendar_read", "calendar_edit")
+ .build())
+ .addAuth(AtnProvider.Auth.builder("service")
+ .type(SubjectType.SERVICE)
+ .roles("service_role")
+ .scopes("calendar_read", "calendar_edit")
+ .build())
+ .build();
+
+ ServiceDescriptor stringService = ServiceDescriptor.builder(new StringService())
+ .intercept("Upper", GrpcSecurity.secure()
+ .customObject(atnConfig)
+ .customObject(validScopes)
+ .customObject(validTimes)
+ .customObject(validPolicy))
+ .build();
+
+ GrpcRouting grpcRouting = GrpcRouting.builder()
+ .intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.secure()))
+ .register(stringService)
+ .build();
+
+ GrpcServerConfiguration serverConfig = GrpcServerConfiguration.builder().build();
+ GrpcServer grpcServer = GrpcServer.create(serverConfig, grpcRouting);
+
+ grpcServer.start()
+ .thenAccept(s -> {
+ System.out.println("gRPC server is UP! http://localhost:" + s.port());
+ s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
+ })
+ .exceptionally(t -> {
+ System.err.println("Startup failed: " + t.getMessage());
+ t.printStackTrace(System.err);
+ return null;
+ });
+ }
+}
diff --git a/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AbacServerFromConfig.java b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AbacServerFromConfig.java
new file mode 100644
index 00000000000..d9606568d73
--- /dev/null
+++ b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AbacServerFromConfig.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.security.abac;
+
+import java.util.logging.LogManager;
+
+import io.helidon.config.Config;
+import io.helidon.grpc.examples.common.StringService;
+import io.helidon.grpc.server.GrpcRouting;
+import io.helidon.grpc.server.GrpcServer;
+import io.helidon.grpc.server.GrpcServerConfiguration;
+import io.helidon.security.Security;
+import io.helidon.security.integration.grpc.GrpcSecurity;
+
+/**
+ * An example of a secure gRPC server that uses ABAC
+ * security configured from configuration the configuration
+ * file application.conf.
+ *
+ * This server's configuration file configures security with
+ * same rules that the {@link AbacServer} class builds in
+ * code.
+ */
+public class AbacServerFromConfig {
+
+ private AbacServerFromConfig() {
+ }
+
+ /**
+ * Main entry point.
+ *
+ * @param args the program arguments
+ *
+ * @throws Exception if an error occurs
+ */
+ public static void main(String[] args) throws Exception {
+ LogManager.getLogManager().readConfiguration(
+ AbacServerFromConfig.class.getResourceAsStream("/logging.properties"));
+
+ Config config = Config.create();
+
+ Security security = Security.create(config.get("security"));
+
+ GrpcRouting grpcRouting = GrpcRouting.builder()
+ .intercept(GrpcSecurity.create(security, config.get("security")))
+ .register(new StringService())
+ .build();
+
+ GrpcServerConfiguration serverConfig = GrpcServerConfiguration.create(config.get("grpc"));
+ GrpcServer grpcServer = GrpcServer.create(serverConfig, grpcRouting);
+
+ grpcServer.start()
+ .thenAccept(s -> {
+ System.out.println("gRPC server is UP! http://localhost:" + s.port());
+ s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
+ })
+ .exceptionally(t -> {
+ System.err.println("Startup failed: " + t.getMessage());
+ t.printStackTrace(System.err);
+ return null;
+ });
+ }
+}
diff --git a/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AtnProvider.java b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AtnProvider.java
new file mode 100644
index 00000000000..0c809bc5ddd
--- /dev/null
+++ b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AtnProvider.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.security.abac;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import io.helidon.common.CollectionsHelper;
+import io.helidon.config.Config;
+import io.helidon.security.AuthenticationResponse;
+import io.helidon.security.EndpointConfig;
+import io.helidon.security.Grant;
+import io.helidon.security.Principal;
+import io.helidon.security.ProviderRequest;
+import io.helidon.security.Role;
+import io.helidon.security.Subject;
+import io.helidon.security.SubjectType;
+import io.helidon.security.spi.AuthenticationProvider;
+import io.helidon.security.spi.SynchronousProvider;
+
+/**
+ * Example authentication provider that reads annotation to create a subject.
+ */
+public class AtnProvider extends SynchronousProvider implements AuthenticationProvider {
+
+ /**
+ * The configuration key for this provider.
+ */
+ public static final String CONFIG_KEY = "atn";
+
+ private final Config config;
+
+ private AtnProvider(Config config) {
+ this.config = config;
+ }
+
+ @Override
+ protected AuthenticationResponse syncAuthenticate(ProviderRequest providerRequest) {
+ EndpointConfig endpointConfig = providerRequest.endpointConfig();
+ Config atnConfig = endpointConfig.config(CONFIG_KEY).orElse(null);
+ Subject user = null;
+ Subject service = null;
+ List list;
+
+ Optional optional = providerRequest.endpointConfig().instance(AtnConfig.class);
+
+ if (optional.isPresent()) {
+ list = optional.get().auths();
+ } else if (atnConfig != null && !atnConfig.isLeaf()) {
+ list = atnConfig.asNodeList()
+ .map(this::fromConfig).orElse(Collections.emptyList());
+ } else {
+ list = fromAnnotations(endpointConfig);
+ }
+
+ for (Auth authentication : list) {
+ if (authentication.type() == SubjectType.USER) {
+ user = buildSubject(authentication);
+ } else {
+ service = buildSubject(authentication);
+ }
+ }
+
+ return AuthenticationResponse.success(user, service);
+ }
+
+ private List fromConfig(List configList) {
+ return configList.stream()
+ .map(Auth::new)
+ .collect(Collectors.toList());
+ }
+
+ private List fromAnnotations(EndpointConfig endpointConfig) {
+ return endpointConfig.combineAnnotations(Authentications.class, EndpointConfig.AnnotationScope.METHOD)
+ .stream()
+ .map(Authentications::value)
+ .flatMap(Arrays::stream)
+ .map(Auth::new)
+ .collect(Collectors.toList());
+ }
+
+ private Subject buildSubject(Auth authentication) {
+ Subject.Builder subjectBuilder = Subject.builder();
+
+ subjectBuilder.principal(Principal.create(authentication.principal()));
+
+ Arrays.stream(authentication.roles())
+ .map(Role::create)
+ .forEach(subjectBuilder::addGrant);
+
+ Arrays.stream(authentication.scopes())
+ .map(scope -> Grant.builder().name(scope).type("scope").build())
+ .forEach(subjectBuilder::addGrant);
+
+ return subjectBuilder.build();
+ }
+
+ @Override
+ public Collection> supportedAnnotations() {
+ return CollectionsHelper.setOf(Authentication.class);
+ }
+
+ /**
+ * Create a {@link AtnProvider}.
+ * @return a {@link AtnProvider}
+ */
+ public static AtnProvider create() {
+ return builder().build();
+ }
+
+ /**
+ * Create a {@link AtnProvider}.
+ *
+ * @param config the configuration for the {@link AtnProvider}
+ *
+ * @return a {@link AtnProvider}
+ */
+ public static AtnProvider create(Config config) {
+ return builder(config).build();
+ }
+
+ /**
+ * Create a {@link AtnProvider.Builder}.
+ * @return a {@link AtnProvider.Builder}
+ */
+ public static Builder builder() {
+ return builder(null);
+ }
+
+ /**
+ * Create a {@link AtnProvider.Builder}.
+ *
+ * @param config the configuration for the {@link AtnProvider}
+ *
+ * @return a {@link AtnProvider.Builder}
+ */
+ public static Builder builder(Config config) {
+ return new Builder(config);
+ }
+
+ /**
+ * A builder that builds {@link AtnProvider} instances.
+ */
+ public static class Builder
+ implements io.helidon.common.Builder {
+
+ private Config config;
+
+ private Builder(Config config) {
+ this.config = config;
+ }
+
+ /**
+ * Set the configuration for the {@link AtnProvider}.
+ * @param config the configuration for the {@link AtnProvider}
+ * @return this builder
+ */
+ public Builder config(Config config) {
+ this.config = config;
+ return this;
+ }
+
+ @Override
+ public AtnProvider build() {
+ return new AtnProvider(config);
+ }
+ }
+
+ /**
+ * Authentication annotation.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD})
+ @Documented
+ @Inherited
+ @Repeatable(Authentications.class)
+ public @interface Authentication {
+ /**
+ * Name of the principal.
+ *
+ * @return principal name
+ */
+ String value();
+
+ /**
+ * Type of the subject, defaults to user.
+ *
+ * @return type
+ */
+ SubjectType type() default SubjectType.USER;
+
+ /**
+ * Granted roles.
+ * @return array of roles
+ */
+ String[] roles() default "";
+
+ /**
+ * Granted scopes.
+ * @return array of scopes
+ */
+ String[] scopes() default "";
+ }
+
+ /**
+ * Repeatable annotation for {@link Authentication}.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD})
+ @Documented
+ @Inherited
+ public @interface Authentications {
+ /**
+ * Repeating annotation.
+ * @return annotations
+ */
+ Authentication[] value();
+ }
+
+ /**
+ * A holder for authentication settings.
+ */
+ public static class Auth {
+ private String principal;
+ private SubjectType type = SubjectType.USER;
+ private String[] roles;
+ private String[] scopes;
+
+ private Auth(Authentication authentication) {
+ principal = authentication.value();
+ type = authentication.type();
+ roles = authentication.roles();
+ scopes = authentication.scopes();
+ }
+
+ private Auth(Config config) {
+ config.get("principal").ifExists(cfg -> principal = cfg.asString().get());
+ config.get("type").ifExists(cfg -> type = SubjectType.valueOf(cfg.asString().get()));
+ config.get("roles").ifExists(cfg -> roles = cfg.asList(String.class).get().toArray(new String[0]));
+ config.get("scopes").ifExists(cfg -> scopes = cfg.asList(String.class).get().toArray(new String[0]));
+ }
+
+ private Auth(String principal, SubjectType type, String[] roles, String[] scopes) {
+ this.principal = principal;
+ this.type = type;
+ this.roles = roles;
+ this.scopes = scopes;
+ }
+
+ private String principal() {
+ return principal;
+ }
+
+ private SubjectType type() {
+ return type;
+ }
+
+ private String[] roles() {
+ return roles;
+ }
+
+ private String[] scopes() {
+ return scopes;
+ }
+
+ /**
+ * Obtain a builder for building {@link Auth} instances.
+ *
+ * @param principal the principal name
+ *
+ * @return a builder for building {@link Auth} instances.
+ */
+ public static Builder builder(String principal) {
+ return new Auth.Builder(principal);
+ }
+
+ /**
+ * A builder for building {@link Auth} instances.
+ */
+ public static class Builder
+ implements io.helidon.common.Builder {
+
+ private final String principal;
+ private SubjectType type = SubjectType.USER;
+ private String[] roles;
+ private String[] scopes;
+
+ private Builder(String principal) {
+ this.principal = principal;
+ }
+
+ /**
+ * Set the {@link SubjectType}.
+ * @param type the {@link SubjectType}
+ * @return this builder
+ */
+ public Builder type(SubjectType type) {
+ this.type = type;
+ return this;
+ }
+
+ /**
+ * Set the roles.
+ * @param roles the role names
+ * @return this builder
+ */
+ public Builder roles(String... roles) {
+ this.roles = roles;
+ return this;
+ }
+
+ /**
+ * Set the scopes.
+ * @param scopes the scopes names
+ * @return this builder
+ */
+ public Builder scopes(String... scopes) {
+ this.scopes = scopes;
+ return this;
+ }
+
+ @Override
+ public Auth build() {
+ return new Auth(principal, type, roles, scopes);
+ }
+ }
+ }
+
+ /**
+ * The configuration for a {@link AtnProvider}.
+ */
+ public static class AtnConfig {
+ private final List authData;
+
+ private AtnConfig(List list) {
+ this.authData = list;
+ }
+
+ /**
+ * Obtain the {@link List} of {@link Auth}s to use.
+ *
+ * @return the {@link List} of {@link Auth}s to use
+ */
+ public List auths() {
+ return Collections.unmodifiableList(authData);
+ }
+
+ /**
+ * Obtain a builder for building {@link AtnConfig} instances.
+ *
+ * @return a builder for building {@link AtnConfig} instances
+ */
+ public static AtnConfig.Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * A builder for building {@link AtnConfig} instances.
+ */
+ public static class Builder
+ implements io.helidon.common.Builder {
+
+ private final List authData = new ArrayList<>();
+
+ /**
+ * Add an {@link Auth} instance.
+ *
+ * @param auth the {@link Auth} to add
+ *
+ * @return this builder
+ *
+ * @throws java.lang.NullPointerException if the {@link Auth} is null
+ */
+ public Builder addAuth(Auth auth) {
+ authData.add(Objects.requireNonNull(auth));
+ return this;
+ }
+
+ @Override
+ public AtnConfig build() {
+ return new AtnConfig(authData);
+ }
+ }
+ }
+}
diff --git a/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AtnProviderService.java b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AtnProviderService.java
new file mode 100644
index 00000000000..64eed4e13e4
--- /dev/null
+++ b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/AtnProviderService.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.security.abac;
+
+import io.helidon.config.Config;
+import io.helidon.security.spi.SecurityProvider;
+import io.helidon.security.spi.SecurityProviderService;
+
+/**
+ * A service provider for the {@link AtnProvider}.
+ */
+public class AtnProviderService
+ implements SecurityProviderService {
+
+ @Override
+ public String providerConfigKey() {
+ return AtnProvider.CONFIG_KEY;
+ }
+
+ @Override
+ public Class extends SecurityProvider> providerClass() {
+ return AtnProvider.class;
+ }
+
+ @Override
+ public SecurityProvider providerInstance(Config config) {
+ return AtnProvider.create(config);
+ }
+}
diff --git a/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/SecureStringClient.java b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/SecureStringClient.java
new file mode 100644
index 00000000000..7b3939342af
--- /dev/null
+++ b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/SecureStringClient.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.security.abac;
+
+
+import io.helidon.grpc.examples.common.StringServiceGrpc;
+import io.helidon.grpc.examples.common.Strings;
+
+import io.grpc.Channel;
+import io.grpc.ManagedChannelBuilder;
+
+/**
+ * A {@link io.helidon.grpc.examples.common.StringService} client that optionally
+ * provides {@link io.grpc.CallCredentials} using basic auth.
+ */
+public class SecureStringClient {
+
+ private SecureStringClient() {
+ }
+
+ /**
+ * Program entry point.
+ *
+ * @param args program arguments
+ */
+ public static void main(String[] args) {
+ Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408)
+ .usePlaintext()
+ .build();
+
+ StringServiceGrpc.StringServiceBlockingStub stub = StringServiceGrpc.newBlockingStub(channel);
+
+ String text = "abcde";
+ Strings.StringMessage request = Strings.StringMessage.newBuilder().setText(text).build();
+ Strings.StringMessage response = stub.upper(request);
+
+ System.out.println("Text '" + text + "' to upper is '" + response.getText() + "'");
+ }
+}
diff --git a/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/package-info.java b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/package-info.java
new file mode 100644
index 00000000000..d9150a74d88
--- /dev/null
+++ b/examples/grpc/security-abac/src/main/java/io/helidon/grpc/examples/security/abac/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A set of small usage examples. Start with {@link io.helidon.grpc.examples.security.SecureServer Main} class.
+ */
+package io.helidon.grpc.examples.security.abac;
diff --git a/examples/grpc/security-abac/src/main/resources/META-INF/services/io.helidon.security.providers.abac.spi.AbacValidator b/examples/grpc/security-abac/src/main/resources/META-INF/services/io.helidon.security.providers.abac.spi.AbacValidator
new file mode 100644
index 00000000000..6a05d845ad6
--- /dev/null
+++ b/examples/grpc/security-abac/src/main/resources/META-INF/services/io.helidon.security.providers.abac.spi.AbacValidator
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+io.helidon.security.abac.scope.ScopeValidator
+io.helidon.security.abac.policy.PolicyValidator
+io.helidon.security.abac.time.TimeValidator
diff --git a/examples/grpc/security-abac/src/main/resources/META-INF/services/io.helidon.security.spi.SecurityProviderService b/examples/grpc/security-abac/src/main/resources/META-INF/services/io.helidon.security.spi.SecurityProviderService
new file mode 100644
index 00000000000..9359c2f3df9
--- /dev/null
+++ b/examples/grpc/security-abac/src/main/resources/META-INF/services/io.helidon.security.spi.SecurityProviderService
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+io.helidon.grpc.examples.security.abac.AtnProviderService
\ No newline at end of file
diff --git a/examples/grpc/security-abac/src/main/resources/application.yaml b/examples/grpc/security-abac/src/main/resources/application.yaml
new file mode 100644
index 00000000000..761d97d2c31
--- /dev/null
+++ b/examples/grpc/security-abac/src/main/resources/application.yaml
@@ -0,0 +1,86 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+grpc:
+ port: 1408
+
+security:
+ providers:
+ - abac:
+ # prepares environment
+ # executes attribute validations
+ # validates that attributes were processed
+ # grants/denies access to resource
+ #
+ ####
+ # Combinations:
+ # # Will fail if any attribute is not validated and if any has failed validation
+ # fail-on-unvalidated: true
+ # fail-if-none-validated: true
+ #
+ # # Will fail if there is one or more attributes present and NONE of them is validated or if any has failed validation
+ # # Will NOT fail if there is at least one validated attribute and any number of not validated attributes (and NONE failed)
+ # fail-on-unvalidated: false
+ # fail-if-none-validated: true
+ #
+ # # Will fail if there is any attribute that failed validation
+ # # Will NOT fail if there are no failed validation or if there are NONE validated
+ # fail-on-unvalidated: false
+ # fail-if-none-validated: false
+ ####
+ # fail if an attribute was not validated (e.g. we do not know, whether it is valid or not)
+ # defaults to true
+ fail-on-unvalidated: true
+ # fail if none of the attributes were validated
+ # defaults to true
+ fail-if-none-validated: true
+ - atn:
+ class: "io.helidon.grpc.examples.security.abac.AtnProvider"
+
+ grpc-server:
+ # Configuration of integration with grpc server
+ # The default configuration to apply to all services not explicitly configured below
+ defaults:
+ authenticate: true
+ authorize: true
+ services:
+ - name: "StringService"
+ methods:
+ - name: "Upper"
+ # Define our custom authenticator rules for the Upper method
+ atn:
+ - principal: "user"
+ type: "USER"
+ roles: ["user_role"]
+ scopes: ["calendar_read", "calendar_edit"]
+ - principal: "service"
+ type: "SERVICE"
+ roles: ["service_role"]
+ scopes: ["calendar_read", "calendar_edit"]
+ # Define ABAC rules for the Upper method
+ abac:
+ scopes: ["calendar_read", "calendar_edit"]
+ time:
+ time-of-day:
+ - from: "08:15:00"
+ to: "12:00:00"
+ - from: "12:30"
+ to: "17:30"
+ days-of-week: ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY"]
+ policy:
+ statement: "${env.time.year >= 2017}"
+
+
diff --git a/examples/grpc/security-abac/src/main/resources/logging.properties b/examples/grpc/security-abac/src/main/resources/logging.properties
new file mode 100644
index 00000000000..13c16d78c52
--- /dev/null
+++ b/examples/grpc/security-abac/src/main/resources/logging.properties
@@ -0,0 +1,21 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+handlers=java.util.logging.ConsoleHandler
+java.util.logging.ConsoleHandler.level=FINEST
+java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
+.level=INFO
+AUDIT.level=FINEST
diff --git a/examples/grpc/security-outbound/README.md b/examples/grpc/security-outbound/README.md
new file mode 100644
index 00000000000..51b5613ada0
--- /dev/null
+++ b/examples/grpc/security-outbound/README.md
@@ -0,0 +1,17 @@
+
+# Helidon gRPC Security ABAC Example
+
+An example gRPC outbound security
+
+## Build
+
+```
+mvn package
+```
+
+## Run
+
+To start the server run:
+```
+mvn exec:java
+```
diff --git a/examples/grpc/security-outbound/pom.xml b/examples/grpc/security-outbound/pom.xml
new file mode 100644
index 00000000000..3318ac4c1cc
--- /dev/null
+++ b/examples/grpc/security-outbound/pom.xml
@@ -0,0 +1,95 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-project
+ 1.0.4-SNAPSHOT
+
+ helidon-examples-grpc-security-outbound
+ Helidon gRPC Server Examples Outbound Security
+
+
+ Examples of outbound security when using gRPC services
+
+
+
+ io.helidon.grpc.examples.security.outbound.SecureServer
+
+
+
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-common
+ ${project.version}
+
+
+
+ io.helidon.grpc
+ helidon-grpc-core
+ ${project.version}
+
+
+ io.helidon.grpc
+ helidon-grpc-server
+ ${project.version}
+
+
+ io.helidon.security.integration
+ helidon-security-integration-grpc
+ ${project.version}
+
+
+ io.helidon.bundles
+ helidon-bundles-config
+ ${project.version}
+
+
+ io.helidon.bundles
+ helidon-bundles-webserver
+ ${project.version}
+
+
+ org.glassfish.jersey.core
+ jersey-client
+
+
+ org.glassfish.jersey.inject
+ jersey-hk2
+
+
+ io.helidon.security.integration
+ helidon-security-integration-jersey
+ ${project.version}
+
+
+ io.helidon.bundles
+ helidon-bundles-security
+ ${project.version}
+
+
+
+ io.helidon.grpc
+ helidon-grpc-client
+ ${project.version}
+
+
+
diff --git a/examples/grpc/security-outbound/src/main/java/io/helidon/grpc/examples/security/outbound/SecureGreetClient.java b/examples/grpc/security-outbound/src/main/java/io/helidon/grpc/examples/security/outbound/SecureGreetClient.java
new file mode 100644
index 00000000000..4b94278195a
--- /dev/null
+++ b/examples/grpc/security-outbound/src/main/java/io/helidon/grpc/examples/security/outbound/SecureGreetClient.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.security.outbound;
+
+
+import io.helidon.config.Config;
+import io.helidon.grpc.examples.common.Greet;
+import io.helidon.grpc.examples.common.GreetServiceGrpc;
+import io.helidon.security.Security;
+import io.helidon.security.integration.grpc.GrpcClientSecurity;
+import io.helidon.security.providers.httpauth.HttpBasicAuthProvider;
+
+import io.grpc.Channel;
+import io.grpc.ManagedChannelBuilder;
+
+/**
+ * A GreetService client that uses {@link io.grpc.CallCredentials} using basic auth.
+ */
+public class SecureGreetClient {
+
+ private SecureGreetClient() {
+ }
+
+ /**
+ * Program entry point.
+ *
+ * @param args program arguments
+ */
+ public static void main(String[] args) {
+ Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408)
+ .usePlaintext()
+ .build();
+
+ Config config = Config.create();
+
+ // configure Helidon security and add the basic auth provider
+ Security security = Security.builder()
+ .addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth")))
+ .build();
+
+ // create the gRPC client security call credentials
+ // setting the properties used by the basic auth provider for user name and password
+ GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client"))
+ .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, "Bob")
+ .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, "password")
+ .build();
+
+ // create the GreetService client stub and use the GrpcClientSecurity call credentials
+ GreetServiceGrpc.GreetServiceBlockingStub stub = GreetServiceGrpc.newBlockingStub(channel)
+ .withCallCredentials(clientSecurity);
+
+ Greet.GreetResponse greetResponse = stub.greet(Greet.GreetRequest.newBuilder().setName("Bob").build());
+
+ System.out.println(greetResponse.getMessage());
+
+ Greet.SetGreetingResponse setGreetingResponse =
+ stub.setGreeting(Greet.SetGreetingRequest.newBuilder().setGreeting("Merhaba").build());
+
+ System.out.println("Greeting set to: " + setGreetingResponse.getGreeting());
+
+ greetResponse = stub.greet(Greet.GreetRequest.newBuilder().setName("Bob").build());
+
+ System.out.println(greetResponse.getMessage());
+ }
+}
diff --git a/examples/grpc/security-outbound/src/main/java/io/helidon/grpc/examples/security/outbound/SecureServer.java b/examples/grpc/security-outbound/src/main/java/io/helidon/grpc/examples/security/outbound/SecureServer.java
new file mode 100644
index 00000000000..7fc13ad2276
--- /dev/null
+++ b/examples/grpc/security-outbound/src/main/java/io/helidon/grpc/examples/security/outbound/SecureServer.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.security.outbound;
+
+import java.util.Optional;
+import java.util.logging.LogManager;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+
+import io.helidon.config.Config;
+import io.helidon.grpc.core.GrpcHelper;
+import io.helidon.grpc.examples.common.Greet;
+import io.helidon.grpc.examples.common.StringService;
+import io.helidon.grpc.examples.common.StringServiceGrpc;
+import io.helidon.grpc.examples.common.Strings;
+import io.helidon.grpc.server.GrpcRouting;
+import io.helidon.grpc.server.GrpcServer;
+import io.helidon.grpc.server.GrpcServerConfiguration;
+import io.helidon.grpc.server.GrpcService;
+import io.helidon.grpc.server.ServiceDescriptor;
+import io.helidon.security.Security;
+import io.helidon.security.SecurityContext;
+import io.helidon.security.integration.grpc.GrpcClientSecurity;
+import io.helidon.security.integration.grpc.GrpcSecurity;
+import io.helidon.security.integration.jersey.ClientSecurityFeature;
+import io.helidon.security.integration.webserver.WebSecurity;
+import io.helidon.security.providers.httpauth.HttpBasicAuthProvider;
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.ServerConfiguration;
+import io.helidon.webserver.ServerRequest;
+import io.helidon.webserver.ServerResponse;
+import io.helidon.webserver.Service;
+import io.helidon.webserver.WebServer;
+
+import io.grpc.Channel;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.stub.StreamObserver;
+
+/**
+ * An example server that configures services with outbound security.
+ */
+public class SecureServer {
+
+ private static GrpcServer grpcServer;
+
+ private static WebServer webServer;
+
+ private SecureServer() {
+ }
+
+ /**
+ * Program entry point.
+ *
+ * @param args the program command line arguments
+ * @throws Exception if there is a program error
+ */
+ public static void main(String[] args) throws Exception {
+ LogManager.getLogManager().readConfiguration(
+ SecureServer.class.getResourceAsStream("/logging.properties"));
+
+ Config config = Config.create();
+
+ Security security = Security.builder()
+ .addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth")))
+ .build();
+
+ grpcServer = createGrpcServer(config.get("grpc"), security);
+ webServer = createWebServer(config.get("webserver"), security);
+ }
+
+ /**
+ * Create the gRPC server.
+ */
+ private static GrpcServer createGrpcServer(Config config, Security security) {
+
+ GrpcRouting grpcRouting = GrpcRouting.builder()
+ // Add the security interceptor with a default of allowing any authenticated user
+ .intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate()))
+ // add the StringService with required role "admin"
+ .register(new StringService(), GrpcSecurity.rolesAllowed("admin"))
+ // add the GreetService (picking up the default security of any authenticated user)
+ .register(new GreetService())
+ .build();
+
+ GrpcServer grpcServer = GrpcServer.create(GrpcServerConfiguration.create(config), grpcRouting);
+
+ grpcServer.start()
+ .thenAccept(s -> {
+ System.out.println("gRPC server is UP! http://localhost:" + s.port());
+ s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
+ })
+ .exceptionally(t -> {
+ System.err.println("gRPC server startup failed: " + t.getMessage());
+ t.printStackTrace(System.err);
+ return null;
+ });
+
+ return grpcServer;
+ }
+
+ /**
+ * Create the web server.
+ */
+ private static WebServer createWebServer(Config config, Security security) {
+
+ Routing routing = Routing.builder()
+ .register(WebSecurity.create(security).securityDefaults(WebSecurity.authenticate()))
+ .register(new RestService())
+ .build();
+
+ WebServer webServer = WebServer.create(ServerConfiguration.create(config), routing);
+
+ webServer.start()
+ .thenAccept(s -> {
+ System.out.println("Web server is UP! http://localhost:" + s.port());
+ s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
+ })
+ .exceptionally(t -> {
+ System.err.println("Web server startup failed: " + t.getMessage());
+ t.printStackTrace(System.err);
+ return null;
+ });
+
+ return webServer;
+ }
+
+ /**
+ * A gRPC greet service that uses outbound security to
+ * access a ReST API.
+ */
+ public static class GreetService
+ implements GrpcService {
+
+ /**
+ * The current greeting.
+ */
+ private String greeting = "hello";
+
+ /**
+ * The JAX-RS client to use to make ReST calls.
+ */
+ private Client client;
+
+ private GreetService() {
+ client = ClientBuilder.newBuilder()
+ .register(new ClientSecurityFeature())
+ .build();
+ }
+
+ @Override
+ public void update(ServiceDescriptor.Rules rules) {
+ rules.proto(Greet.getDescriptor())
+ .unary("Greet", this::greet)
+ .unary("SetGreeting", this::setGreeting);
+ }
+
+ /**
+ * This method calls a secure ReST endpoint using the caller's credentials.
+ *
+ * @param request the request
+ * @param observer the observer to send the response to
+ */
+ private void greet(Greet.GreetRequest request, StreamObserver observer) {
+ // Obtain the greeting name from the request (default to "World".
+ String name = Optional.ofNullable(request.getName()).orElse("World");
+
+ // Obtain the security context from the current gRPC context
+ SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get();
+
+ // Use the current credentials call the "lower" ReST endpoint which will call
+ // the "Lower" method on the secure gRPC StringService.
+ Response response = client.target("http://127.0.0.1:" + webServer.port())
+ .path("lower")
+ .queryParam("value", name)
+ .request()
+ .property(ClientSecurityFeature.PROPERTY_CONTEXT, securityContext)
+ .get();
+
+ int status = response.getStatus();
+
+ if (status == 200) {
+ // Send the response to the caller of the current greeting and lower case name
+ String nameLower = response.readEntity(String.class);
+ String msg = String.format("%s %s!", greeting, nameLower);
+ complete(observer, Greet.GreetResponse.newBuilder().setMessage(msg).build());
+ } else {
+ completeWithError(response, observer);
+ }
+ }
+
+ /**
+ * This method calls a secure ReST endpoint overriding the caller's credentials and
+ * using the admin user's credentials.
+ *
+ * @param request the request
+ * @param observer the observer to send the response to
+ */
+ private void setGreeting(Greet.SetGreetingRequest request, StreamObserver observer) {
+ // Obtain the greeting name from the request (default to "hello".
+ String name = Optional.ofNullable(request.getGreeting()).orElse("hello");
+
+ // Obtain the security context from the current gRPC context
+ SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get();
+
+ // Use the admin user's credentials call the "upper" ReST endpoint which will call
+ // the "Upper" method on the secure gRPC StringService.
+ Response response = client.target("http://127.0.0.1:" + webServer.port())
+ .path("upper")
+ .queryParam("value", name)
+ .request()
+ .property(ClientSecurityFeature.PROPERTY_CONTEXT, securityContext)
+ .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, "Ted")
+ .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, "secret")
+ .get();
+
+ if (response.getStatus() == 200) {
+ greeting = response.readEntity(String.class);
+ complete(observer, Greet.SetGreetingResponse.newBuilder().setGreeting(greeting).build());
+ } else {
+ completeWithError(response, observer);
+ }
+ }
+
+ private void completeWithError(Response response, StreamObserver observer) {
+ int status = response.getStatus();
+
+ if (status == Response.Status.UNAUTHORIZED.getStatusCode()
+ || status == Response.Status.FORBIDDEN.getStatusCode()){
+ observer.onError(Status.PERMISSION_DENIED.asRuntimeException());
+ } else {
+ observer.onError(Status.INTERNAL.withDescription(response.readEntity(String.class)).asRuntimeException());
+ }
+ }
+
+ @Override
+ public String name() {
+ return "GreetService";
+ }
+ }
+
+ /**
+ * A ReST service that calls the gRPC StringService to mutate String values.
+ */
+ public static class RestService
+ implements Service {
+
+ private Channel channel;
+
+ @Override
+ public void update(Routing.Rules rules) {
+ rules.get("/lower", WebSecurity.rolesAllowed("user"), this::lower)
+ .get("/upper", WebSecurity.rolesAllowed("user"), this::upper);
+ }
+
+ /**
+ * Call the gRPC StringService Lower method overriding the caller's credentials and
+ * using the admin user's credentials.
+ *
+ * @param req the http request
+ * @param res the http response
+ */
+ private void lower(ServerRequest req, ServerResponse res) {
+ try {
+ // Create the gRPC client security credentials from the current request
+ // overriding with the admin user's credentials
+ GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(req)
+ .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, "Ted")
+ .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, "secret")
+ .build();
+
+ StringServiceGrpc.StringServiceBlockingStub stub = StringServiceGrpc.newBlockingStub(ensureChannel())
+ .withCallCredentials(clientSecurity);
+
+ String value = req.queryParams().first("value").orElse(null);
+ Strings.StringMessage response = stub.lower(Strings.StringMessage.newBuilder().setText(value).build());
+
+ res.status(200).send(response.getText());
+ } catch (StatusRuntimeException e) {
+ res.status(GrpcHelper.toHttpResponseStatus(e.getStatus())).send();
+ }
+ }
+
+ /**
+ * Call the gRPC StringService Upper method using the current caller's credentials.
+ *
+ * @param req the http request
+ * @param res the http response
+ */
+ private void upper(ServerRequest req, ServerResponse res) {
+ try {
+ // Create the gRPC client security credentials from the current request
+ GrpcClientSecurity clientSecurity = GrpcClientSecurity.create(req);
+
+ StringServiceGrpc.StringServiceBlockingStub stub = StringServiceGrpc.newBlockingStub(ensureChannel())
+ .withCallCredentials(clientSecurity);
+
+ String value = req.queryParams().first("value").orElse(null);
+ Strings.StringMessage response = stub.upper(Strings.StringMessage.newBuilder().setText(value).build());
+
+ res.status(200).send(response.getText());
+ } catch (StatusRuntimeException e) {
+ res.status(GrpcHelper.toHttpResponseStatus(e.getStatus())).send();
+ }
+ }
+
+ private synchronized Channel ensureChannel() {
+ if (channel == null) {
+ channel = InProcessChannelBuilder.forName(grpcServer.configuration().name()).build();
+ }
+ return channel;
+ }
+ }
+}
diff --git a/examples/grpc/security-outbound/src/main/java/io/helidon/grpc/examples/security/outbound/package-info.java b/examples/grpc/security-outbound/src/main/java/io/helidon/grpc/examples/security/outbound/package-info.java
new file mode 100644
index 00000000000..c498917470d
--- /dev/null
+++ b/examples/grpc/security-outbound/src/main/java/io/helidon/grpc/examples/security/outbound/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Examples of using outbound security with gRPC services.
+ */
+package io.helidon.grpc.examples.security.outbound;
diff --git a/examples/grpc/security-outbound/src/main/resources/application.yaml b/examples/grpc/security-outbound/src/main/resources/application.yaml
new file mode 100644
index 00000000000..bfdc19d8882
--- /dev/null
+++ b/examples/grpc/security-outbound/src/main/resources/application.yaml
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+app:
+ greeting: "Hello"
+
+grpc:
+ name: "test.server"
+ port: 1408
+
+webserver:
+ port: 8080
+
+http-basic-auth:
+ users:
+ - login: "Ted"
+ password: "secret"
+ roles: ["user", "admin"]
+ - login: "Bob"
+ password: "password"
+ roles: ["user"]
\ No newline at end of file
diff --git a/examples/grpc/security-outbound/src/main/resources/logging.properties b/examples/grpc/security-outbound/src/main/resources/logging.properties
new file mode 100644
index 00000000000..dcb9fcb30c9
--- /dev/null
+++ b/examples/grpc/security-outbound/src/main/resources/logging.properties
@@ -0,0 +1,39 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=java.util.logging.ConsoleHandler
+
+# Global default logging level. Can be overriden by specific handlers and loggers
+.level=INFO
+
+AUDIT.level=FINEST
+
+# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
+# It replaces "!thread!" with the current thread name
+java.util.logging.ConsoleHandler.level=FINEST
+java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+#Component specific log levels
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
+#io.netty.level=INFO
diff --git a/examples/grpc/security/README.md b/examples/grpc/security/README.md
new file mode 100644
index 00000000000..96fea02168d
--- /dev/null
+++ b/examples/grpc/security/README.md
@@ -0,0 +1,16 @@
+
+# Helidon gRPC Security Example
+
+An example gRPC server using basic auth security.
+
+## Build
+
+```
+mvn package
+```
+
+## Run
+
+```
+mvn exec:java
+```
diff --git a/examples/grpc/security/pom.xml b/examples/grpc/security/pom.xml
new file mode 100644
index 00000000000..d46194b2491
--- /dev/null
+++ b/examples/grpc/security/pom.xml
@@ -0,0 +1,94 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-project
+ 1.0.4-SNAPSHOT
+
+ helidon-examples-grpc-security
+ Helidon gRPC Server Examples Security
+
+
+ Examples of securing gRPC services
+
+
+
+ io.helidon.grpc.examples.security.SecureServer
+
+
+
+
+ io.helidon.examples.grpc
+ helidon-examples-grpc-common
+ ${project.version}
+
+
+
+ io.helidon.grpc
+ helidon-grpc-core
+ ${project.version}
+
+
+ io.helidon.grpc
+ helidon-grpc-server
+ ${project.version}
+
+
+ io.helidon.bundles
+ helidon-bundles-config
+ ${project.version}
+
+
+ io.helidon.security.integration
+ helidon-security-integration-grpc
+ ${project.version}
+
+
+ io.helidon.security.providers
+ helidon-security-providers-http-auth
+ ${project.version}
+
+
+
+ io.helidon.grpc
+ helidon-grpc-client
+ ${project.version}
+
+
+
+ io.grpc
+ grpc-netty
+
+
+ io.grpc
+ grpc-stub
+
+
+ io.grpc
+ grpc-services
+
+
+ io.grpc
+ grpc-protobuf
+
+
+
diff --git a/examples/grpc/security/src/main/java/io/helidon/grpc/examples/security/SecureGreetClient.java b/examples/grpc/security/src/main/java/io/helidon/grpc/examples/security/SecureGreetClient.java
new file mode 100644
index 00000000000..154bc9f3ccb
--- /dev/null
+++ b/examples/grpc/security/src/main/java/io/helidon/grpc/examples/security/SecureGreetClient.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.security;
+
+
+import io.helidon.config.Config;
+import io.helidon.grpc.examples.common.Greet;
+import io.helidon.grpc.examples.common.GreetServiceGrpc;
+import io.helidon.security.Security;
+import io.helidon.security.integration.grpc.GrpcClientSecurity;
+import io.helidon.security.providers.httpauth.HttpBasicAuthProvider;
+
+import io.grpc.CallCredentials;
+import io.grpc.Channel;
+import io.grpc.ManagedChannelBuilder;
+
+/**
+ * A {@link io.helidon.grpc.examples.common.GreetService} client that optionally
+ * provides {@link CallCredentials} using basic auth.
+ */
+public class SecureGreetClient {
+
+ private SecureGreetClient() {
+ }
+
+ /**
+ * Main entry point.
+ *
+ * @param args the program arguments - {@code arg[0]} is the user name
+ * and {@code arg[1] is the password}
+ */
+ public static void main(String[] args) {
+ Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408)
+ .usePlaintext()
+ .build();
+
+ // Obtain the user name and password from the program arguments
+ String user = args.length >= 2 ? args[0] : null;
+ String password = args.length >= 2 ? args[1] : null;
+
+ Config config = Config.create();
+
+ // configure Helidon security and add the basic auth provider
+ Security security = Security.builder()
+ .addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth")))
+ .build();
+
+ // create the gRPC client security call credentials
+ // setting the properties used by the basic auth provider for user name and password
+ GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client"))
+ .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, user)
+ .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, password)
+ .build();
+
+ // create the GreetService client stub and use the GrpcClientSecurity call credentials
+ GreetServiceGrpc.GreetServiceBlockingStub greetSvc = GreetServiceGrpc.newBlockingStub(channel)
+ .withCallCredentials(clientSecurity);
+
+ greet(greetSvc);
+ setGreeting(greetSvc);
+ greet(greetSvc);
+ }
+
+ private static void greet(GreetServiceGrpc.GreetServiceBlockingStub greetSvc) {
+ try {
+ Greet.GreetRequest request = Greet.GreetRequest.newBuilder().setName("Aleks").build();
+ Greet.GreetResponse response = greetSvc.greet(request);
+
+ System.out.println(response);
+ } catch (Exception e) {
+ System.err.println("Caught exception obtaining greeting: " + e.getMessage());
+ }
+ }
+
+ private static void setGreeting(GreetServiceGrpc.GreetServiceBlockingStub greetSvc) {
+ try {
+ Greet.SetGreetingRequest setRequest = Greet.SetGreetingRequest.newBuilder().setGreeting("Hey").build();
+ Greet.SetGreetingResponse setResponse = greetSvc.setGreeting(setRequest);
+
+ System.out.println(setResponse);
+ } catch (Exception e) {
+ System.err.println("Caught exception setting greeting: " + e.getMessage());
+ }
+ }
+}
diff --git a/examples/grpc/security/src/main/java/io/helidon/grpc/examples/security/SecureServer.java b/examples/grpc/security/src/main/java/io/helidon/grpc/examples/security/SecureServer.java
new file mode 100644
index 00000000000..ab519abbb6b
--- /dev/null
+++ b/examples/grpc/security/src/main/java/io/helidon/grpc/examples/security/SecureServer.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.security;
+
+import java.util.logging.LogManager;
+
+import io.helidon.config.Config;
+import io.helidon.grpc.examples.common.GreetService;
+import io.helidon.grpc.examples.common.StringService;
+import io.helidon.grpc.server.GrpcRouting;
+import io.helidon.grpc.server.GrpcServer;
+import io.helidon.grpc.server.GrpcServerConfiguration;
+import io.helidon.grpc.server.ServiceDescriptor;
+import io.helidon.security.Security;
+import io.helidon.security.integration.grpc.GrpcSecurity;
+import io.helidon.security.providers.httpauth.HttpBasicAuthProvider;
+
+/**
+ * An example of a secure gRPC server.
+ */
+public class SecureServer {
+
+ private SecureServer() {
+ }
+
+ /**
+ * Main entry point.
+ *
+ * @param args the program arguments
+ *
+ * @throws Exception if an error occurs
+ */
+ public static void main(String[] args) throws Exception {
+ LogManager.getLogManager().readConfiguration(
+ SecureServer.class.getResourceAsStream("/logging.properties"));
+
+ Config config = Config.create();
+
+ Security security = Security.builder()
+ .addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth")))
+ .build();
+
+ ServiceDescriptor greetService1 = ServiceDescriptor.builder(new GreetService(config))
+ .name("GreetService")
+ .intercept(GrpcSecurity.rolesAllowed("user"))
+ .intercept("SetGreeting", GrpcSecurity.rolesAllowed("admin"))
+ .build();
+
+ GrpcRouting grpcRouting = GrpcRouting.builder()
+ .intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate()))
+ .register(greetService1)
+ .register(new StringService())
+ .build();
+
+ GrpcServerConfiguration serverConfig = GrpcServerConfiguration.create(config.get("grpc"));
+ GrpcServer grpcServer = GrpcServer.create(serverConfig, grpcRouting);
+
+ grpcServer.start()
+ .thenAccept(s -> {
+ System.out.println("gRPC server is UP! http://localhost:" + s.port());
+ s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
+ })
+ .exceptionally(t -> {
+ System.err.println("Startup failed: " + t.getMessage());
+ t.printStackTrace(System.err);
+ return null;
+ });
+ }
+}
diff --git a/examples/grpc/security/src/main/java/io/helidon/grpc/examples/security/SecureStringClient.java b/examples/grpc/security/src/main/java/io/helidon/grpc/examples/security/SecureStringClient.java
new file mode 100644
index 00000000000..376b7db8e37
--- /dev/null
+++ b/examples/grpc/security/src/main/java/io/helidon/grpc/examples/security/SecureStringClient.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.examples.security;
+
+
+import io.helidon.config.Config;
+import io.helidon.grpc.examples.common.StringServiceGrpc;
+import io.helidon.grpc.examples.common.Strings;
+import io.helidon.security.Security;
+import io.helidon.security.integration.grpc.GrpcClientSecurity;
+import io.helidon.security.providers.httpauth.HttpBasicAuthProvider;
+
+import io.grpc.CallCredentials;
+import io.grpc.Channel;
+import io.grpc.ManagedChannelBuilder;
+
+/**
+ * A {@link io.helidon.grpc.examples.common.StringService} client that optionally
+ * provides {@link CallCredentials} using basic auth.
+ */
+public class SecureStringClient {
+
+ private SecureStringClient() {
+ }
+
+ /**
+ * Program entry point.
+ *
+ * @param args the program arguments - {@code arg[0]} is the user name
+ * and {@code arg[1] is the password}
+ */
+ public static void main(String[] args) {
+ Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408)
+ .usePlaintext()
+ .build();
+
+ // Obtain the user name and password from the program arguments
+ String user = args.length >= 2 ? args[0] : null;
+ String password = args.length >= 2 ? args[1] : null;
+
+ Config config = Config.create();
+
+ // configure Helidon security and add the basic auth provider
+ Security security = Security.builder()
+ .addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth")))
+ .build();
+
+ // create the gRPC client security call credentials
+ // setting the properties used by the basic auth provider for user name and password
+ GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client"))
+ .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, user)
+ .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, password)
+ .build();
+
+ // create the StringService client stub and use the GrpcClientSecurity call credentials
+ StringServiceGrpc.StringServiceBlockingStub stub = StringServiceGrpc.newBlockingStub(channel)
+ .withCallCredentials(clientSecurity);
+
+ String text = "ABCDE";
+ Strings.StringMessage request = Strings.StringMessage.newBuilder().setText(text).build();
+ Strings.StringMessage response = stub.lower(request);
+
+ System.out.println("Text '" + text + "' to lower is '" + response.getText() + "'");
+ }
+}
diff --git a/examples/grpc/security/src/main/java/io/helidon/grpc/examples/security/package-info.java b/examples/grpc/security/src/main/java/io/helidon/grpc/examples/security/package-info.java
new file mode 100644
index 00000000000..25aaaab44ba
--- /dev/null
+++ b/examples/grpc/security/src/main/java/io/helidon/grpc/examples/security/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A set of small usage examples. Start with {@link io.helidon.grpc.examples.security.SecureServer Main} class.
+ */
+package io.helidon.grpc.examples.security;
diff --git a/examples/grpc/security/src/main/resources/application.yaml b/examples/grpc/security/src/main/resources/application.yaml
new file mode 100644
index 00000000000..bfdc19d8882
--- /dev/null
+++ b/examples/grpc/security/src/main/resources/application.yaml
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+app:
+ greeting: "Hello"
+
+grpc:
+ name: "test.server"
+ port: 1408
+
+webserver:
+ port: 8080
+
+http-basic-auth:
+ users:
+ - login: "Ted"
+ password: "secret"
+ roles: ["user", "admin"]
+ - login: "Bob"
+ password: "password"
+ roles: ["user"]
\ No newline at end of file
diff --git a/examples/grpc/security/src/main/resources/logging.properties b/examples/grpc/security/src/main/resources/logging.properties
new file mode 100644
index 00000000000..dcb9fcb30c9
--- /dev/null
+++ b/examples/grpc/security/src/main/resources/logging.properties
@@ -0,0 +1,39 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=java.util.logging.ConsoleHandler
+
+# Global default logging level. Can be overriden by specific handlers and loggers
+.level=INFO
+
+AUDIT.level=FINEST
+
+# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
+# It replaces "!thread!" with the current thread name
+java.util.logging.ConsoleHandler.level=FINEST
+java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+#Component specific log levels
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
+#io.netty.level=INFO
diff --git a/examples/integrations/cdi/jpa/pom.xml b/examples/integrations/cdi/jpa/pom.xml
new file mode 100644
index 00000000000..b6672207117
--- /dev/null
+++ b/examples/integrations/cdi/jpa/pom.xml
@@ -0,0 +1,245 @@
+
+
+
+ 4.0.0
+
+ io.helidon.examples.integrations.cdi
+ helidon-examples-integrations-cdi-project
+ 1.0.4-SNAPSHOT
+
+ helidon-integrations-examples-jpa
+ Helidon CDI Extensions Examples JPA
+
+
+ libs
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+ com.ethlo.persistence.tools
+ eclipselink-maven-plugin
+ 2.7.1.1
+
+
+ javax.annotation
+ javax.annotation-api
+ ${version.lib.annotation-api}
+
+
+ javax.xml.bind
+ jaxb-api
+ ${version.lib.jaxb-api}
+
+
+
+
+ weave
+ process-classes
+
+ weave
+
+
+
+ modelgen
+ generate-sources
+
+ modelgen
+
+
+
+
+
+ org.jboss.jandex
+ jandex-maven-plugin
+
+
+ make-index
+
+ jandex
+
+
+
+
+
+ maven-dependency-plugin
+
+
+ copy-dependencies
+ prepare-package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/${dependenciesDirectory}
+ false
+ false
+ true
+ true
+ runtime
+ test
+
+
+
+
+
+ maven-jar-plugin
+
+
+
+ true
+ ${dependenciesDirectory}
+ io.helidon.microprofile.server.Main
+
+
+
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
+
+ com.h2database
+ h2
+ 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-eclipselink
+ ${project.version}
+ runtime
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-jta-weld
+ ${project.version}
+ runtime
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-datasource-hikaricp
+ ${project.version}
+ runtime
+
+
+ io.helidon.integrations.cdi
+ helidon-integrations-cdi-jpa-weld
+ ${project.version}
+ runtime
+
+
+ org.jboss
+ jandex
+ runtime
+
+
+ io.helidon.microprofile.server
+ helidon-microprofile-server
+ ${project.version}
+ runtime
+
+
+ org.glassfish.hk2.external
+ javax.inject
+
+
+
+
+ io.helidon.microprofile.config
+ helidon-microprofile-config-cdi
+ ${project.version}
+ runtime
+
+
+ org.eclipse.microprofile.config
+ microprofile-config-api
+ runtime
+
+
+
+
+ jakarta.persistence
+ jakarta.persistence-api
+ provided
+
+
+ javax.transaction
+ javax.transaction-api
+ provided
+
+
+
+
+ javax.annotation
+ javax.annotation-api
+ compile
+
+
+ javax.enterprise
+ cdi-api
+ compile
+
+
+ javax.inject
+ javax.inject
+ compile
+
+
+ javax.ws.rs
+ javax.ws.rs-api
+ compile
+
+
+
diff --git a/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/Greeting.java b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/Greeting.java
new file mode 100644
index 00000000000..50999617f87
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/Greeting.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2019 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.examples.integrations.cdi.jpa;
+
+import java.util.Objects;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+/**
+ * A contrived representation for example purposes only of a two-part
+ * greeting as might be stored in a database.
+ */
+@Access(AccessType.FIELD)
+@Entity(name = "Greeting")
+@Table(name = "GREETING")
+public class Greeting {
+
+ @Id
+ @Column(name = "FIRSTPART", insertable = true, nullable = false, updatable = false)
+ private String firstPart;
+
+ @Basic(optional = false)
+ @Column(name = "SECONDPART", insertable = true, nullable = false, updatable = true)
+ private String secondPart;
+
+ /**
+ * Creates a new {@link Greeting}; required by the JPA
+ * specification and for no other purpose.
+ *
+ * @deprecated Please use the {@link #Greeting(String,
+ * String)} constructor instead.
+ *
+ * @see #Greeting(String, String)
+ */
+ @Deprecated
+ protected Greeting() {
+ super();
+ }
+
+ /**
+ * Creates a new {@link Greeting}.
+ *
+ * @param firstPart the first part of the greeting; must not be
+ * {@code null}
+ *
+ * @param secondPart the second part of the greeting; must not be
+ * {@code null}
+ *
+ * @exception NullPointerException if {@code firstPart} or {@code
+ * secondPart} is {@code null}
+ */
+ public Greeting(final String firstPart, final String secondPart) {
+ super();
+ this.firstPart = Objects.requireNonNull(firstPart);
+ this.secondPart = Objects.requireNonNull(secondPart);
+ }
+
+ /**
+ * Sets the second part of this greeting.
+ *
+ * @param secondPart the second part of this greeting; must not be
+ * {@code null}
+ *
+ * @exception NullPointerException if {@code secondPart} is {@code
+ * null}
+ */
+ public void setSecondPart(final String secondPart) {
+ this.secondPart = Objects.requireNonNull(secondPart);
+ }
+
+ /**
+ * Returns a {@link String} representation of the second part of
+ * this {@link Greeting}.
+ *
+ *
This method never returns {@code null}.
+ *
+ * @return a non-{@code null} {@link String} representation of the
+ * second part of this {@link Greeting}
+ */
+ @Override
+ public String toString() {
+ return this.secondPart;
+ }
+
+}
diff --git a/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/HelloWorldApplication.java b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/HelloWorldApplication.java
new file mode 100644
index 00000000000..d7f9c1cc9d9
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/HelloWorldApplication.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 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.examples.integrations.cdi.jpa;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.ws.rs.core.Application;
+
+/**
+ * An example {@link Application} demonstrating the modular
+ * integration of JPA and JTA with Helidon MicroProfile.
+ */
+@ApplicationScoped
+public class HelloWorldApplication extends Application {
+
+ private final Set> classes;
+
+ /**
+ * Creates a new {@link HelloWorldApplication}.
+ */
+ public HelloWorldApplication() {
+ super();
+ final Set> classes = new HashSet<>();
+ classes.add(HelloWorldResource.class);
+ classes.add(JPAExceptionMapper.class);
+ this.classes = Collections.unmodifiableSet(classes);
+ }
+
+ /**
+ * Returns a non-{@code null} {@link Set} of {@link Class}es that
+ * comprise this JAX-RS application.
+ *
+ * @return a non-{@code null}, {@linkplain
+ * Collections#unmodifiableSet(Set) unmodifiable Set}
+ *
+ * @see HelloWorldResource
+ */
+ @Override
+ public Set> getClasses() {
+ return this.classes;
+ }
+
+}
diff --git a/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/HelloWorldResource.java b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/HelloWorldResource.java
new file mode 100644
index 00000000000..7965af9161e
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/HelloWorldResource.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.examples.integrations.cdi.jpa;
+
+import java.net.URI;
+import java.util.Objects;
+
+import javax.enterprise.context.RequestScoped;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityNotFoundException;
+import javax.persistence.PersistenceContext;
+import javax.persistence.PersistenceException; // for javadoc only
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.Transactional;
+import javax.transaction.Transactional.TxType;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+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;
+
+/**
+ * A JAX-RS root resource class that manipulates greetings in a
+ * database.
+ *
+ * @see #get(String)
+ *
+ * @see #post(String, String)
+ */
+@Path("")
+@RequestScoped
+public class HelloWorldResource {
+
+ /**
+ * The {@link EntityManager} used by this class.
+ *
+ *
Note that it behaves as though there is a transaction manager
+ * in effect, because there is.
+ */
+ @PersistenceContext(unitName = "test")
+ private EntityManager entityManager;
+
+ /**
+ * A {@link Transaction} that is guaranteed to be non-{@code null}
+ * only when a transactional method is executing.
+ *
+ * @see #post(String, String)
+ */
+ @Inject
+ private Transaction transaction;
+
+ /**
+ * Creates a new {@link HelloWorldResource}.
+ */
+ public HelloWorldResource() {
+ super();
+ }
+
+ /**
+ * Returns a {@link Response} with a status of {@code 404} when
+ * invoked.
+ *
+ * @return a non-{@code null} {@link Response}
+ */
+ @GET
+ @Path("favicon.ico")
+ public Response getFavicon() {
+ return Response.status(404).build();
+ }
+
+ /**
+ * When handed a {@link String} like, say, "{@code hello}", responds
+ * with the second part of the composite greeting as found via an
+ * {@link EntityManager}.
+ *
+ * @param firstPart the first part of the greeting; must not be
+ * {@code null}
+ *
+ * @return the second part of the greeting; never {@code null}
+ *
+ * @exception NullPointerException if {@code firstPart} was {@code
+ * null}
+ *
+ * @exception PersistenceException if the {@link EntityManager}
+ * encountered an error
+ */
+ @GET
+ @Path("{firstPart}")
+ @Produces(MediaType.TEXT_PLAIN)
+ public String get(@PathParam("firstPart") final String firstPart) {
+ Objects.requireNonNull(firstPart);
+ assert this.entityManager != null;
+ final Greeting greeting = this.entityManager.find(Greeting.class, firstPart);
+ assert greeting != null;
+ return greeting.toString();
+ }
+
+ /**
+ * When handed two parts of a greeting, like, say, "{@code hello}"
+ * and "{@code world}", stores a new {@link Greeting} entity in the
+ * database appropriately.
+ *
+ * @param firstPart the first part of the greeting; must not be
+ * {@code null}
+ *
+ * @param secondPart the second part of the greeting; must not be
+ * {@code null}
+ *
+ * @return the {@link String} representation of the resulting {@link
+ * Greeting}'s identifier; never {@code null}
+ *
+ * @exception NullPointerException if {@code firstPart} or {@code
+ * secondPart} was {@code null}
+ *
+ * @exception PersistenceException if the {@link EntityManager}
+ * encountered an error
+ *
+ * @exception SystemException if something went wrong with the
+ * transaction
+ */
+ @POST
+ @Path("{firstPart}")
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Produces(MediaType.TEXT_PLAIN)
+ @Transactional(TxType.REQUIRED)
+ public Response post(@PathParam("firstPart") final String firstPart,
+ final String secondPart)
+ throws SystemException {
+ Objects.requireNonNull(firstPart);
+ Objects.requireNonNull(secondPart);
+ assert this.transaction != null;
+ assert this.transaction.getStatus() == Status.STATUS_ACTIVE;
+ assert this.entityManager != null;
+ assert this.entityManager.isJoinedToTransaction();
+ Greeting greeting = null;
+ // See https://tools.ietf.org/html/rfc7231#section-4.3.3; we
+ // track whether JPA does an insert or an update.
+ boolean created = false;
+ try {
+ greeting = this.entityManager.getReference(Greeting.class, firstPart);
+ assert greeting != null;
+ greeting.setSecondPart(secondPart);
+ } catch (final EntityNotFoundException entityNotFoundException) {
+ greeting = new Greeting(firstPart, secondPart);
+ this.entityManager.persist(greeting);
+ created = true;
+ }
+ assert this.entityManager.contains(greeting);
+ if (created) {
+ return Response.created(URI.create(firstPart)).build();
+ } else {
+ return Response.ok(firstPart).build();
+ }
+ }
+
+}
diff --git a/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/JPAExceptionMapper.java b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/JPAExceptionMapper.java
new file mode 100644
index 00000000000..51c1fb720ee
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/JPAExceptionMapper.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2019 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.examples.integrations.cdi.jpa;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.persistence.EntityNotFoundException;
+import javax.persistence.NoResultException;
+import javax.persistence.PersistenceException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * An {@link ExceptionMapper} that handles {@link
+ * PersistenceException}s.
+ *
+ * @see ExceptionMapper
+ */
+@ApplicationScoped
+@Provider
+public class JPAExceptionMapper implements ExceptionMapper {
+
+ /**
+ * Creates a new {@link JPAExceptionMapper}.
+ */
+ public JPAExceptionMapper() {
+ super();
+ }
+
+ /**
+ * Returns an appropriate non-{@code null} {@link Response} for the
+ * supplied {@link PersistenceException}.
+ *
+ * @param persistenceException the {@link PersistenceException} that
+ * caused this {@link JPAExceptionMapper} to be invoked; may be
+ * {@code null}
+ *
+ * @return a non-{@code null} {@link Response} representing the
+ * error
+ */
+ @Override
+ public Response toResponse(final PersistenceException persistenceException) {
+ final Response returnValue;
+ if (persistenceException instanceof NoResultException
+ || persistenceException instanceof EntityNotFoundException) {
+ returnValue = Response.status(404).build();
+ } else {
+ returnValue = null;
+ throw persistenceException;
+ }
+ return returnValue;
+ }
+
+}
diff --git a/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/package-info.java b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/package-info.java
new file mode 100644
index 00000000000..fe314923757
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/java/io/helidon/examples/integrations/cdi/jpa/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2019 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 demonstrating the usage of JPA and
+ * JTA integration within Helidon MicroProfile.
+ */
+package io.helidon.examples.integrations.cdi.jpa;
diff --git a/examples/integrations/cdi/jpa/src/main/resources/META-INF/beans.xml b/examples/integrations/cdi/jpa/src/main/resources/META-INF/beans.xml
new file mode 100644
index 00000000000..89f9c163080
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/integrations/cdi/jpa/src/main/resources/META-INF/microprofile-config.properties b/examples/integrations/cdi/jpa/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 00000000000..09f0967ec67
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+javax.sql.DataSource.test.dataSourceClassName=org.h2.jdbcx.JdbcDataSource
+javax.sql.DataSource.test.dataSource.url=jdbc:h2:mem:test;INIT=CREATE TABLE GREETING (FIRSTPART VARCHAR NOT NULL, SECONDPART VARCHAR NOT NULL, PRIMARY KEY (FIRSTPART))\\;INSERT INTO GREETING (FIRSTPART, SECONDPART) VALUES ('hello', 'world')
+javax.sql.DataSource.test.username=sa
+javax.sql.DataSource.test.password=
diff --git a/examples/integrations/cdi/jpa/src/main/resources/META-INF/persistence.xml b/examples/integrations/cdi/jpa/src/main/resources/META-INF/persistence.xml
new file mode 100644
index 00000000000..108d28d641e
--- /dev/null
+++ b/examples/integrations/cdi/jpa/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,52 @@
+
+
+
+
+ test
+ io.helidon.examples.integrations.cdi.jpa.Greeting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/integrations/cdi/pom.xml b/examples/integrations/cdi/pom.xml
index 3e066f938a3..b4e32749f3e 100644
--- a/examples/integrations/cdi/pom.xml
+++ b/examples/integrations/cdi/pom.xml
@@ -35,6 +35,7 @@
datasource-hikaricpdatasource-hikaricp-mysqljedis
+ jpaoci-objectstorage
diff --git a/examples/pom.xml b/examples/pom.xml
index c7d66fdafd7..0f7c7b94673 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -45,6 +45,7 @@
todo-appquickstartshealth
+ grpc
diff --git a/examples/quickstarts/helidon-quickstart-mp/build.gradle b/examples/quickstarts/helidon-quickstart-mp/build.gradle
index 10378f75c47..32bcad36835 100644
--- a/examples/quickstarts/helidon-quickstart-mp/build.gradle
+++ b/examples/quickstarts/helidon-quickstart-mp/build.gradle
@@ -33,7 +33,7 @@ ext {
}
repositories {
- maven { url "http://repo.maven.apache.org/maven2" }
+ mavenCentral()
mavenLocal()
}
diff --git a/examples/quickstarts/helidon-quickstart-se/build.gradle b/examples/quickstarts/helidon-quickstart-se/build.gradle
index cfc97e3d2da..5b45588506c 100644
--- a/examples/quickstarts/helidon-quickstart-se/build.gradle
+++ b/examples/quickstarts/helidon-quickstart-se/build.gradle
@@ -33,7 +33,7 @@ ext {
}
repositories {
- maven { url "http://repo.maven.apache.org/maven2" }
+ mavenCentral()
mavenLocal()
}
diff --git a/grpc/client/pom.xml b/grpc/client/pom.xml
new file mode 100644
index 00000000000..1a44c362fb3
--- /dev/null
+++ b/grpc/client/pom.xml
@@ -0,0 +1,134 @@
+
+
+
+
+ 4.0.0
+
+
+ io.helidon.grpc
+ helidon-grpc-project
+ 1.0.4-SNAPSHOT
+
+
+ helidon-grpc-client
+ Helidon gRPC Client
+
+
+
+ io.helidon.grpc
+ helidon-grpc-core
+ ${project.version}
+
+
+
+ io.helidon.tracing
+ helidon-tracing
+ ${project.version}
+
+
+ io.helidon.tracing
+ helidon-tracing-zipkin
+ ${project.version}
+
+
+ io.opentracing.contrib
+ opentracing-grpc
+
+
+
+ io.helidon.grpc
+ helidon-grpc-server
+ ${project.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ javax.json
+ javax.json-api
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ ${version.plugin.os}
+
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+
+
+
+ test-compile
+ test-compile-custom
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+ false
+
+ **/*IT.java
+
+
+
+
+ verify
+ verify
+
+ integration-test
+ verify
+
+
+
+
+
+
+
diff --git a/grpc/client/src/main/java/io/helidon/grpc/client/ClientRequestAttribute.java b/grpc/client/src/main/java/io/helidon/grpc/client/ClientRequestAttribute.java
new file mode 100644
index 00000000000..d9def8a2a01
--- /dev/null
+++ b/grpc/client/src/main/java/io/helidon/grpc/client/ClientRequestAttribute.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.grpc.client;
+
+/**
+ * An enum of possible gRPC client call attributes to attach to
+ * call tracing spans.
+ */
+public enum ClientRequestAttribute {
+ /**
+ * Add the method type to the tracing span.
+ */
+ METHOD_TYPE,
+
+ /**
+ * Add the method name to the tracing span.
+ */
+ METHOD_NAME,
+
+ /**
+ * Add the call deadline to the tracing span.
+ */
+ DEADLINE,
+
+ /**
+ * Add the compressor type to the tracing span.
+ */
+ COMPRESSOR,
+
+ /**
+ * Add the security authority to the tracing span.
+ */
+ AUTHORITY,
+
+ /**
+ * Add the method call options to the tracing span.
+ */
+ ALL_CALL_OPTIONS,
+
+ /**
+ * Add the method call headers to the tracing span.
+ */
+ HEADERS
+}
diff --git a/grpc/client/src/main/java/io/helidon/grpc/client/ClientTracingInterceptor.java b/grpc/client/src/main/java/io/helidon/grpc/client/ClientTracingInterceptor.java
new file mode 100644
index 00000000000..8d84137bab1
--- /dev/null
+++ b/grpc/client/src/main/java/io/helidon/grpc/client/ClientTracingInterceptor.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.client;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Priority;
+
+import io.helidon.grpc.core.ContextKeys;
+import io.helidon.grpc.core.InterceptorPriorities;
+
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ClientInterceptors;
+import io.grpc.ForwardingClientCall;
+import io.grpc.ForwardingClientCallListener;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.opentracing.Span;
+import io.opentracing.Tracer;
+import io.opentracing.contrib.grpc.ActiveSpanSource;
+import io.opentracing.contrib.grpc.OperationNameConstructor;
+import io.opentracing.propagation.Format;
+import io.opentracing.propagation.TextMap;
+
+/**
+ * A {@link ClientInterceptor} that captures tracing information into
+ * Open Tracing {@link Span}s for client calls.
+ */
+@Priority(InterceptorPriorities.TRACING)
+public class ClientTracingInterceptor
+ implements ClientInterceptor {
+
+ private final Tracer tracer;
+
+ private final OperationNameConstructor operationNameConstructor;
+
+ private final boolean streaming;
+
+ private final boolean verbose;
+
+ private final Set tracedAttributes;
+
+ private final ActiveSpanSource activeSpanSource;
+
+ /**
+ * Private constructor called by {@link Builder}.
+ *
+ * @param tracer the Open Tracing {@link Tracer}
+ * @param operationNameConstructor the operation name constructor
+ * @param streaming flag indicating whether to trace streaming calls
+ * @param verbose flag to indicate verbose logging to spans
+ * @param tracedAttributes the set of request attributes to add to the span
+ * @param activeSpanSource the spurce of the active span
+ */
+ private ClientTracingInterceptor(Tracer tracer,
+ OperationNameConstructor operationNameConstructor,
+ boolean streaming,
+ boolean verbose,
+ Set tracedAttributes,
+ ActiveSpanSource activeSpanSource) {
+ this.tracer = tracer;
+ this.operationNameConstructor = operationNameConstructor;
+ this.streaming = streaming;
+ this.verbose = verbose;
+ this.tracedAttributes = tracedAttributes;
+ this.activeSpanSource = activeSpanSource;
+ }
+
+ /**
+ * Use this interceptor to trace all requests made by this client channel.
+ *
+ * @param channel to be traced
+ * @return intercepted channel
+ */
+ public Channel intercept(Channel channel) {
+ return ClientInterceptors.intercept(channel, this);
+ }
+
+ @Override
+ public ClientCall interceptCall(MethodDescriptor method,
+ CallOptions callOptions,
+ Channel next) {
+
+ String operationName = operationNameConstructor.constructOperationName(method);
+ Span span = createSpanFromParent(activeSpanSource.getActiveSpan(), operationName);
+
+ for (ClientRequestAttribute attr : tracedAttributes) {
+ switch (attr) {
+ case ALL_CALL_OPTIONS:
+ span.setTag("grpc.call_options", callOptions.toString());
+ break;
+ case AUTHORITY:
+ if (callOptions.getAuthority() == null) {
+ span.setTag("grpc.authority", "null");
+ } else {
+ span.setTag("grpc.authority", callOptions.getAuthority());
+ }
+ break;
+ case COMPRESSOR:
+ if (callOptions.getCompressor() == null) {
+ span.setTag("grpc.compressor", "null");
+ } else {
+ span.setTag("grpc.compressor", callOptions.getCompressor());
+ }
+ break;
+ case DEADLINE:
+ if (callOptions.getDeadline() == null) {
+ span.setTag("grpc.deadline_millis", "null");
+ } else {
+ span.setTag("grpc.deadline_millis", callOptions.getDeadline().timeRemaining(TimeUnit.MILLISECONDS));
+ }
+ break;
+ case METHOD_NAME:
+ span.setTag("grpc.method_name", method.getFullMethodName());
+ break;
+ case METHOD_TYPE:
+ if (method.getType() == null) {
+ span.setTag("grpc.method_type", "null");
+ } else {
+ span.setTag("grpc.method_type", method.getType().toString());
+ }
+ break;
+ case HEADERS:
+ break;
+ default:
+ // should not happen, but can be ignored
+ }
+ }
+
+ return new ClientTracingListener<>(next.newCall(method, callOptions), span);
+ }
+
+ private Span createSpanFromParent(Span parentSpan, String operationName) {
+ if (parentSpan == null) {
+ return tracer.buildSpan(operationName).start();
+ } else {
+ return tracer.buildSpan(operationName).asChildOf(parentSpan).start();
+ }
+ }
+
+ /**
+ * Obtain a builder to build a {@link ClientTracingInterceptor}.
+ *
+ * @param tracer the {@link Tracer} to use
+ *
+ * @return a builder to build a {@link ClientTracingInterceptor}
+ */
+ public static Builder builder(Tracer tracer) {
+ return new Builder(tracer);
+ }
+
+ /**
+ * Builds the configuration of a ClientTracingInterceptor.
+ */
+ public static class Builder {
+
+ private final Tracer tracer;
+
+ private OperationNameConstructor operationNameConstructor;
+
+ private boolean streaming;
+
+ private boolean verbose;
+
+ private Set tracedAttributes;
+
+ private ActiveSpanSource activeSpanSource;
+
+ /**
+ * @param tracer to use for this intercepter
+ * Creates a Builder with default configuration
+ */
+ public Builder(Tracer tracer) {
+ this.tracer = tracer;
+ operationNameConstructor = OperationNameConstructor.DEFAULT;
+ streaming = false;
+ verbose = false;
+ tracedAttributes = new HashSet<>();
+ activeSpanSource = ActiveSpanSource.GRPC_CONTEXT;
+ }
+
+ /**
+ * @param operationNameConstructor to name all spans created by this intercepter
+ * @return this Builder with configured operation name
+ */
+ public ClientTracingInterceptor.Builder withOperationName(OperationNameConstructor operationNameConstructor) {
+ this.operationNameConstructor = operationNameConstructor;
+ return this;
+ }
+
+ /**
+ * Logs streaming events to client spans.
+ *
+ * @return this Builder configured to log streaming events
+ */
+ public ClientTracingInterceptor.Builder withStreaming() {
+ streaming = true;
+ return this;
+ }
+
+ /**
+ * @param tracedAttributes to set as tags on client spans
+ * created by this intercepter
+ * @return this Builder configured to trace attributes
+ */
+ public ClientTracingInterceptor.Builder withTracedAttributes(ClientRequestAttribute... tracedAttributes) {
+ this.tracedAttributes = new HashSet<>(Arrays.asList(tracedAttributes));
+ return this;
+ }
+
+ /**
+ * Logs all request life-cycle events to client spans.
+ *
+ * @return this Builder configured to be verbose
+ */
+ public ClientTracingInterceptor.Builder withVerbosity() {
+ verbose = true;
+ return this;
+ }
+
+ /**
+ * @param activeSpanSource that provides a method of getting the
+ * active span before the client call
+ * @return this Builder configured to start client span as children
+ * of the span returned by activeSpanSource.getActiveSpan()
+ */
+ public ClientTracingInterceptor.Builder withActiveSpanSource(ActiveSpanSource activeSpanSource) {
+ this.activeSpanSource = activeSpanSource;
+ return this;
+ }
+
+ /**
+ * @return a ClientTracingInterceptor with this Builder's configuration
+ */
+ public ClientTracingInterceptor build() {
+ return new ClientTracingInterceptor(tracer,
+ operationNameConstructor,
+ streaming,
+ verbose,
+ tracedAttributes,
+ activeSpanSource);
+ }
+ }
+
+ /**
+ * A {@link ForwardingClientCall.SimpleForwardingClientCall} that adds information
+ * to a tracing {@link Span} at different places in the gROC call lifecycle.
+ *
+ * @param the gRPC request type
+ * @param the gRPC response type
+ */
+ private class ClientTracingListener
+ extends ForwardingClientCall.SimpleForwardingClientCall {
+
+ private final Span span;
+
+ private ClientTracingListener(ClientCall delegate, Span span) {
+ super(delegate);
+ this.span = span;
+ }
+
+ @Override
+ public void start(Listener responseListener, final Metadata headers) {
+ if (verbose) {
+ span.log("Started call");
+ }
+
+ if (tracedAttributes.contains(ClientRequestAttribute.HEADERS)) {
+ // copy the headers and make sure that the AUTHORIZATION header
+ // is removed as we do not want auth details to appear in tracing logs
+ Metadata metadata = new Metadata();
+ metadata.merge(headers);
+ metadata.removeAll(ContextKeys.AUTHORIZATION);
+ span.setTag("grpc.headers", metadata.toString());
+ }
+
+ tracer.inject(span.context(), Format.Builtin.HTTP_HEADERS, new TextMap() {
+ @Override
+ public void put(String key, String value) {
+ Metadata.Key headerKey = Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER);
+ headers.put(headerKey, value);
+ }
+
+ @Override
+ public Iterator> iterator() {
+ throw new UnsupportedOperationException(
+ "TextMapInjectAdapter should only be used with Tracer.inject()");
+ }
+ });
+
+ Listener tracingResponseListener
+ = new ForwardingClientCallListener.SimpleForwardingClientCallListener(responseListener) {
+ @Override
+ public void onHeaders(Metadata headers) {
+ if (verbose) {
+ span.log(Collections.singletonMap("Response headers received", headers.toString()));
+ }
+ delegate().onHeaders(headers);
+ }
+
+ @Override
+ public void onMessage(RespT message) {
+ if (streaming || verbose) {
+ span.log("Response received");
+ }
+ delegate().onMessage(message);
+ }
+
+ @Override
+ public void onClose(Status status, Metadata trailers) {
+ if (verbose) {
+ if (status.getCode().value() == 0) {
+ span.log("Call closed");
+ } else {
+ String desc = String.valueOf(status.getDescription());
+
+ span.log(Collections.singletonMap("Call failed", desc));
+ }
+ }
+ span.finish();
+
+ delegate().onClose(status, trailers);
+ }
+ };
+
+ delegate().start(tracingResponseListener, headers);
+ }
+
+ @Override
+ public void cancel(String message, Throwable cause) {
+ String errorMessage;
+
+ errorMessage = message == null ? "Error" : message;
+
+ if (cause == null) {
+ span.log(errorMessage);
+ } else {
+ span.log(Collections.singletonMap(errorMessage, cause.getMessage()));
+ }
+
+ delegate().cancel(message, cause);
+ }
+
+ @Override
+ public void halfClose() {
+ if (streaming) {
+ span.log("Finished sending messages");
+ }
+
+ delegate().halfClose();
+ }
+
+ @Override
+ public void sendMessage(ReqT message) {
+ if (streaming || verbose) {
+ span.log("Message sent");
+ }
+
+ delegate().sendMessage(message);
+ }
+ }
+}
diff --git a/grpc/client/src/main/java/io/helidon/grpc/client/package-info.java b/grpc/client/src/main/java/io/helidon/grpc/client/package-info.java
new file mode 100644
index 00000000000..2a9d4c99de5
--- /dev/null
+++ b/grpc/client/src/main/java/io/helidon/grpc/client/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * gRPC client API.
+ */
+package io.helidon.grpc.client;
diff --git a/grpc/client/src/test/java/io/helidon/grpc/client/TestServer.java b/grpc/client/src/test/java/io/helidon/grpc/client/TestServer.java
new file mode 100644
index 00000000000..aa1bbaecf7f
--- /dev/null
+++ b/grpc/client/src/test/java/io/helidon/grpc/client/TestServer.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.client;
+
+import java.util.logging.LogManager;
+
+import io.helidon.grpc.server.GrpcRouting;
+import io.helidon.grpc.server.GrpcServer;
+import io.helidon.grpc.server.GrpcServerConfiguration;
+
+import services.EchoService;
+
+/**
+ * A test gRPC server.
+ */
+public class TestServer {
+ /**
+ * Program entry point.
+ *
+ * @param args the program command line arguments
+ * @throws Exception if there is a program error
+ */
+ public static void main(String[] args) throws Exception {
+ LogManager.getLogManager().readConfiguration(TestServer.class.getResourceAsStream("/logging.properties"));
+
+ // Add the EchoService and enable GrpcMetrics
+ GrpcRouting routing = GrpcRouting.builder()
+ .register(new EchoService())
+ .build();
+
+ // Run the server on port 0 so that it picks a free ephemeral port
+ GrpcServerConfiguration serverConfig = GrpcServerConfiguration.builder().build();
+
+ GrpcServer.create(serverConfig, routing)
+ .start()
+ .thenAccept(s -> {
+ System.out.println("gRPC server is UP and listening on localhost:" + s.port());
+ s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
+ })
+ .exceptionally(t -> {
+ System.err.println("Startup failed: " + t.getMessage());
+ t.printStackTrace(System.err);
+ return null;
+ });
+ }
+}
diff --git a/grpc/client/src/test/java/services/EchoService.java b/grpc/client/src/test/java/services/EchoService.java
new file mode 100644
index 00000000000..dd5e1c7c663
--- /dev/null
+++ b/grpc/client/src/test/java/services/EchoService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package services;
+
+import io.helidon.grpc.server.GrpcService;
+import io.helidon.grpc.server.ServiceDescriptor;
+import io.helidon.grpc.client.test.Echo;
+
+import io.grpc.stub.StreamObserver;
+
+/**
+ * A simple test gRPC echo service.
+ */
+public class EchoService
+ implements GrpcService {
+
+ @Override
+ public void update(ServiceDescriptor.Rules rules) {
+ rules.proto(Echo.getDescriptor())
+ .unary("Echo", this::echo);
+ }
+
+ /**
+ * Echo the message back to the caller.
+ *
+ * @param request the echo request containing the message to echo
+ * @param observer the call response
+ */
+ public void echo(Echo.EchoRequest request, StreamObserver observer) {
+ String message = request.getMessage();
+ Echo.EchoResponse response = Echo.EchoResponse.newBuilder().setMessage(message).build();
+ complete(observer, response);
+ }
+}
diff --git a/grpc/client/src/test/proto/echo.proto b/grpc/client/src/test/proto/echo.proto
new file mode 100644
index 00000000000..cf46b367fef
--- /dev/null
+++ b/grpc/client/src/test/proto/echo.proto
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+option java_package = "io.helidon.grpc.client.test";
+
+service EchoService {
+ rpc Echo (EchoRequest) returns (EchoResponse) {}
+}
+
+message EchoRequest {
+ string message = 1;
+}
+
+message EchoResponse {
+ string message = 1;
+}
diff --git a/grpc/client/src/test/resources/logging.properties b/grpc/client/src/test/resources/logging.properties
new file mode 100644
index 00000000000..f903114790c
--- /dev/null
+++ b/grpc/client/src/test/resources/logging.properties
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=java.util.logging.ConsoleHandler
+
+# Global default logging level. Can be overriden by specific handlers and loggers
+.level=INFO
+
+# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
+# It replaces "!thread!" with the current thread name
+java.util.logging.ConsoleHandler.level=INFO
+java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+#Component specific log levels
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
+#io.netty.level=INFO
diff --git a/grpc/core/pom.xml b/grpc/core/pom.xml
new file mode 100644
index 00000000000..4f1e8b4d81f
--- /dev/null
+++ b/grpc/core/pom.xml
@@ -0,0 +1,88 @@
+
+
+
+
+ 4.0.0
+
+
+ io.helidon.grpc
+ helidon-grpc-project
+ 1.0.4-SNAPSHOT
+
+
+ helidon-grpc-core
+ Helidon gRPC Core
+
+
+
+ io.grpc
+ grpc-netty
+
+
+ io.grpc
+ grpc-stub
+
+
+ io.grpc
+ grpc-services
+
+
+ io.grpc
+ grpc-protobuf
+
+
+
+ io.helidon.common
+ helidon-common-http
+ ${project.version}
+
+
+
+ javax.inject
+ javax.inject
+
+
+ javax.annotation
+ javax.annotation-api
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ javax.json
+ javax.json-api
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
diff --git a/grpc/core/src/main/java/io/helidon/grpc/core/ContextKeys.java b/grpc/core/src/main/java/io/helidon/grpc/core/ContextKeys.java
new file mode 100644
index 00000000000..98e76b7a0d7
--- /dev/null
+++ b/grpc/core/src/main/java/io/helidon/grpc/core/ContextKeys.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.core;
+
+import io.grpc.Metadata;
+
+/**
+ * A collection of common gRPC {@link io.grpc.Context.Key} instances.
+ */
+public final class ContextKeys {
+ /**
+ * The authorization gRPC metadata header key.
+ */
+ public static final Metadata.Key AUTHORIZATION =
+ Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER);
+
+ /**
+ * Private constructor for utility class.
+ */
+ private ContextKeys() {
+ }
+}
diff --git a/grpc/core/src/main/java/io/helidon/grpc/core/GrpcHelper.java b/grpc/core/src/main/java/io/helidon/grpc/core/GrpcHelper.java
new file mode 100644
index 00000000000..bbe598d7d83
--- /dev/null
+++ b/grpc/core/src/main/java/io/helidon/grpc/core/GrpcHelper.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.core;
+
+import io.helidon.common.http.Http;
+
+import io.grpc.Status;
+import io.grpc.StatusException;
+import io.grpc.StatusRuntimeException;
+
+/**
+ * Helper methods for common gRPC tasks.
+ */
+public final class GrpcHelper {
+
+ /**
+ * Private constructor for utility class.
+ */
+ private GrpcHelper() {
+ }
+
+ /**
+ * Extract the gRPC service name from a method full name.
+ *
+ * @param fullMethodName the gRPC method full name
+ *
+ * @return the service name extracted from the full name
+ */
+ public static String extractServiceName(String fullMethodName) {
+ int index = fullMethodName.indexOf('/');
+ return index == -1 ? fullMethodName : fullMethodName.substring(0, index);
+ }
+
+ /**
+ * Extract the name prefix from from a method full name.
+ *
+ * The prefix is everything upto the but not including the last
+ * '/' character in the full name.
+ *
+ * @param fullMethodName the gRPC method full name
+ *
+ * @return the name prefix extracted from the full name
+ */
+ public static String extractNamePrefix(String fullMethodName) {
+ int index = fullMethodName.lastIndexOf('/');
+ return index == -1 ? fullMethodName : fullMethodName.substring(0, index);
+ }
+
+ /**
+ * Extract the gRPC method name from a method full name.
+ *
+ * @param fullMethodName the gRPC method full name
+ *
+ * @return the method name extracted from the full name
+ */
+ public static String extractMethodName(String fullMethodName) {
+ int index = fullMethodName.lastIndexOf('/');
+ return index == -1 ? fullMethodName : fullMethodName.substring(index + 1);
+ }
+
+ /**
+ * Convert a gRPC {@link StatusException} to a {@link Http.ResponseStatus}.
+ *
+ * @param ex the gRPC {@link StatusException} to convert
+ *
+ * @return the gRPC {@link StatusException} converted to a {@link Http.ResponseStatus}
+ */
+ public static Http.ResponseStatus toHttpResponseStatus(StatusException ex) {
+ return toHttpResponseStatus(ex.getStatus());
+ }
+
+ /**
+ * Convert a gRPC {@link StatusRuntimeException} to a {@link Http.ResponseStatus}.
+ *
+ * @param ex the gRPC {@link StatusRuntimeException} to convert
+ *
+ * @return the gRPC {@link StatusRuntimeException} converted to a {@link Http.ResponseStatus}
+ */
+ public static Http.ResponseStatus toHttpResponseStatus(StatusRuntimeException ex) {
+ return toHttpResponseStatus(ex.getStatus());
+ }
+
+ /**
+ * Convert a gRPC {@link Status} to a {@link Http.ResponseStatus}.
+ *
+ * @param status the gRPC {@link Status} to convert
+ *
+ * @return the gRPC {@link Status} converted to a {@link Http.ResponseStatus}
+ */
+ public static Http.ResponseStatus toHttpResponseStatus(Status status) {
+ Http.ResponseStatus httpStatus;
+
+ switch (status.getCode()) {
+ case OK:
+ httpStatus = Http.ResponseStatus.create(200, status.getDescription());
+ break;
+ case INVALID_ARGUMENT:
+ httpStatus = Http.ResponseStatus.create(400, status.getDescription());
+ break;
+ case DEADLINE_EXCEEDED:
+ httpStatus = Http.ResponseStatus.create(408, status.getDescription());
+ break;
+ case NOT_FOUND:
+ httpStatus = Http.ResponseStatus.create(404, status.getDescription());
+ break;
+ case ALREADY_EXISTS:
+ httpStatus = Http.ResponseStatus.create(412, status.getDescription());
+ break;
+ case PERMISSION_DENIED:
+ httpStatus = Http.ResponseStatus.create(403, status.getDescription());
+ break;
+ case FAILED_PRECONDITION:
+ httpStatus = Http.ResponseStatus.create(412, status.getDescription());
+ break;
+ case OUT_OF_RANGE:
+ httpStatus = Http.ResponseStatus.create(400, status.getDescription());
+ break;
+ case UNIMPLEMENTED:
+ httpStatus = Http.ResponseStatus.create(501, status.getDescription());
+ break;
+ case UNAVAILABLE:
+ httpStatus = Http.ResponseStatus.create(503, status.getDescription());
+ break;
+ case UNAUTHENTICATED:
+ httpStatus = Http.ResponseStatus.create(401, status.getDescription());
+ break;
+ case ABORTED:
+ case CANCELLED:
+ case DATA_LOSS:
+ case INTERNAL:
+ case RESOURCE_EXHAUSTED:
+ case UNKNOWN:
+ default:
+ httpStatus = Http.ResponseStatus.create(500, status.getDescription());
+ }
+
+ return httpStatus;
+ }
+}
diff --git a/grpc/core/src/main/java/io/helidon/grpc/core/InterceptorPriorities.java b/grpc/core/src/main/java/io/helidon/grpc/core/InterceptorPriorities.java
new file mode 100644
index 00000000000..fab090c477d
--- /dev/null
+++ b/grpc/core/src/main/java/io/helidon/grpc/core/InterceptorPriorities.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.core;
+
+/**
+ * Constants that represent a priority ordering that interceptors registered with
+ * a gRPC service or method will be applied.
+ */
+public class InterceptorPriorities {
+ /**
+ * Context priority.
+ *
+ * Interceptors with this priority typically only perform tasks
+ * such as adding state to the call {@link io.grpc.Context}.
+ */
+ public static final int CONTEXT = 1000;
+
+ /**
+ * Tracing priority.
+ *
+ * Tracing and metrics interceptors are typically applied after any context
+ * interceptors so that they can trace and gather metrics on the whole call
+ * stack of remaining interceptors.
+ */
+ public static final int TRACING = CONTEXT + 1;
+
+ /**
+ * Security authentication priority.
+ */
+ public static final int AUTHENTICATION = 2000;
+
+ /**
+ * Security authorization priority.
+ */
+ public static final int AUTHORIZATION = 2000;
+
+ /**
+ * User-level priority.
+ *
+ * This value is also used as a default priority for application-supplied interceptors.
+ */
+ public static final int USER = 5000;
+
+ /**
+ * Cannot create instances.
+ */
+ private InterceptorPriorities() {
+ }
+}
diff --git a/grpc/core/src/main/java/io/helidon/grpc/core/JavaMarshaller.java b/grpc/core/src/main/java/io/helidon/grpc/core/JavaMarshaller.java
new file mode 100644
index 00000000000..bbc1a84c467
--- /dev/null
+++ b/grpc/core/src/main/java/io/helidon/grpc/core/JavaMarshaller.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.core;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import io.grpc.MethodDescriptor;
+
+/**
+ * An implementation of a gRPC {@link MethodDescriptor.Marshaller} that
+ * uses Java serialization.
+ *
+ * @param the type of value to to be marshalled
+ */
+@Singleton
+@Named("java")
+public class JavaMarshaller
+ implements MethodDescriptor.Marshaller {
+
+ @Override
+ public InputStream stream(T obj) {
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(out)) {
+ oos.writeObject(obj);
+ return new ByteArrayInputStream(out.toByteArray());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T parse(InputStream in) {
+ try (ObjectInputStream ois = new ObjectInputStream(in)) {
+ return (T) ois.readObject();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/grpc/core/src/main/java/io/helidon/grpc/core/MarshallerSupplier.java b/grpc/core/src/main/java/io/helidon/grpc/core/MarshallerSupplier.java
new file mode 100644
index 00000000000..96dbc4aea9c
--- /dev/null
+++ b/grpc/core/src/main/java/io/helidon/grpc/core/MarshallerSupplier.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.core;
+
+import com.google.protobuf.MessageLite;
+import io.grpc.MethodDescriptor;
+import io.grpc.protobuf.lite.ProtoLiteUtils;
+
+/**
+ * A supplier of {@link MethodDescriptor.Marshaller} instances for specific
+ * classes.
+ */
+@FunctionalInterface
+public interface MarshallerSupplier {
+
+ /**
+ * Obtain a {@link MethodDescriptor.Marshaller} for a type.
+ *
+ * @param clazz the {@link Class} of the type to obtain the {@link MethodDescriptor.Marshaller} for
+ * @param the type to be marshalled
+ *
+ * @return a {@link MethodDescriptor.Marshaller} for a type
+ */
+ MethodDescriptor.Marshaller get(Class clazz);
+
+ /**
+ * Obtain the default marshaller.
+ *
+ * @return the default marshaller
+ */
+ static MarshallerSupplier defaultInstance() {
+ return new DefaultMarshallerSupplier();
+ }
+
+ /**
+ * The default {@link MarshallerSupplier}.
+ */
+ class DefaultMarshallerSupplier
+ implements MarshallerSupplier {
+
+ /**
+ * The singleton default {@link MethodDescriptor.Marshaller}.
+ */
+ private static final MethodDescriptor.Marshaller JAVA_MARSHALLER = new JavaMarshaller();
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public MethodDescriptor.Marshaller get(Class clazz) {
+ if (MessageLite.class.isAssignableFrom(clazz)) {
+ try {
+ java.lang.reflect.Method getDefaultInstance = clazz.getDeclaredMethod("getDefaultInstance");
+ MessageLite instance = (MessageLite) getDefaultInstance.invoke(clazz);
+ return (MethodDescriptor.Marshaller) ProtoLiteUtils.marshaller(instance);
+ } catch (Exception e) {
+ String msg = String.format(
+ "Attempting to use class %s, which is not a valid Protobuf message, with a default marshaller",
+ clazz.getName());
+ throw new IllegalArgumentException(msg);
+ }
+ }
+
+ return JAVA_MARSHALLER;
+ }
+ }
+}
diff --git a/grpc/core/src/main/java/io/helidon/grpc/core/PriorityBag.java b/grpc/core/src/main/java/io/helidon/grpc/core/PriorityBag.java
new file mode 100644
index 00000000000..fc5a1f39be8
--- /dev/null
+++ b/grpc/core/src/main/java/io/helidon/grpc/core/PriorityBag.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.core;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.stream.Stream;
+
+import javax.annotation.Priority;
+
+import io.helidon.common.Prioritized;
+
+/**
+ * A bag of values ordered by priority.
+ *
+ * An element with lower priority number is more significant than an element
+ * with a higher priority number.
+ *
+ * For cases where priority is the same, elements are ordered in the order that
+ * they were added to the bag.
+ *
+ * Elements added with negative priorities are assumed to have no priority and
+ * will be least significant in order.
+ *
+ * @param the type of elements in the bag
+ */
+public class PriorityBag implements Iterable {
+
+ private final Map> contents;
+
+ private final List noPriorityList;
+
+ private final int defaultPriority;
+
+ /**
+ * Create a new {@link PriorityBag} where elements
+ * added with no priority will be last in the order.
+ */
+ public PriorityBag() {
+ this(new TreeMap<>(), new ArrayList<>(), -1);
+ }
+
+ /**
+ * Create a new {@link PriorityBag} where elements
+ * added with no priority will be be given a default
+ * priority value.
+ *
+ * @param defaultPriority the default priority value to assign
+ * to elements added with no priority
+ */
+ public PriorityBag(int defaultPriority) {
+ this(new TreeMap<>(), new ArrayList<>(), defaultPriority);
+ }
+
+
+ private PriorityBag(Map> contents, List noPriorityList, int defaultPriority) {
+ this.contents = contents;
+ this.noPriorityList = noPriorityList;
+ this.defaultPriority = defaultPriority;
+ }
+
+ /**
+ * Obtain a copy of this {@link PriorityBag}.
+ *
+ * @return a copy of this {@link PriorityBag}
+ */
+ public PriorityBag copyMe() {
+ PriorityBag copy = new PriorityBag<>();
+ copy.merge(this);
+ return copy;
+ }
+
+ /**
+ * Obtain an immutable copy of this {@link PriorityBag}.
+ *
+ * @return an immutable copy of this {@link PriorityBag}
+ */
+ public PriorityBag readOnly() {
+ return new PriorityBag<>(Collections.unmodifiableMap(contents),
+ Collections.unmodifiableList(noPriorityList),
+ defaultPriority);
+ }
+
+ /**
+ * Merge a {@link PriorityBag} into this {@link PriorityBag}.
+ *
+ * @param bag the bag to merge
+ */
+ public void merge(PriorityBag extends T> bag) {
+ bag.contents.forEach((priority, value) -> addAll(value, priority));
+ this.noPriorityList.addAll(bag.noPriorityList);
+ }
+
+ /**
+ * Add elements to the bag.
+ *
+ * If the element's class is annotated with the {@link javax.annotation.Priority}
+ * annotation then that value will be used to determine priority otherwise the
+ * default priority value will be used.
+ *
+ * @param values the elements to add
+ */
+ public void addAll(Iterable extends T> values) {
+ for (T value : values) {
+ add(value);
+ }
+ }
+
+ /**
+ * Add elements to the bag.
+ *
+ * @param values the elements to add
+ * @param priority the priority to assign to the elements
+ */
+ public void addAll(Iterable extends T> values, int priority) {
+ for (T value : values) {
+ add(value, priority);
+ }
+ }
+
+ /**
+ * Add an element to the bag.
+ *
+ * If the element's class is annotated with the {@link javax.annotation.Priority}
+ * annotation then that value will be used to determine priority otherwise the
+ * default priority value will be used.
+ *
+ * @param value the element to add
+ */
+ public void add(T value) {
+ if (value != null) {
+ int priority;
+ if (value instanceof Prioritized) {
+ priority = ((Prioritized) value).priority();
+ } else {
+ Priority annotation = value.getClass().getAnnotation(Priority.class);
+ priority = annotation == null ? defaultPriority : annotation.value();
+ }
+ add(value, priority);
+ }
+ }
+
+ /**
+ * Add an element to the bag with a specific priority.
+ *
+ *
+ * @param value the element to add
+ * @param priority the priority of the element
+ */
+ public void add(T value, int priority) {
+ if (value != null) {
+ if (priority < 0) {
+ noPriorityList.add(value);
+ } else {
+ contents.compute(priority, (key, list) -> combine(list, value));
+ }
+ }
+ }
+
+ /**
+ * Obtain the contents of this {@link PriorityBag} as
+ * an ordered {@link Stream}.
+ *
+ * @return the contents of this {@link PriorityBag} as
+ * an ordered {@link Stream}
+ */
+ public Stream stream() {
+ Stream stream = contents.entrySet()
+ .stream()
+ .flatMap(e -> e.getValue().stream());
+
+ return Stream.concat(stream, noPriorityList.stream());
+ }
+
+ @Override
+ public Iterator iterator() {
+ return stream().iterator();
+ }
+
+ private List combine(List list, T value) {
+ if (list == null) {
+ list = new ArrayList<>();
+ }
+ list.add(value);
+ return list;
+ }
+}
diff --git a/grpc/core/src/main/java/io/helidon/grpc/core/SafeStreamObserver.java b/grpc/core/src/main/java/io/helidon/grpc/core/SafeStreamObserver.java
new file mode 100644
index 00000000000..553dbbe5910
--- /dev/null
+++ b/grpc/core/src/main/java/io/helidon/grpc/core/SafeStreamObserver.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.core;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+
+/**
+ * A {@link io.grpc.stub.StreamObserver} that handles exceptions correctly.
+ *
+ * @param the type of response expected
+ */
+public class SafeStreamObserver
+ implements StreamObserver {
+
+ /**
+ * Create a {@link io.helidon.grpc.core.SafeStreamObserver} that wraps
+ * another {@link io.grpc.stub.StreamObserver}.
+ *
+ * @param streamObserver the {@link io.grpc.stub.StreamObserver} to wrap
+ */
+ public SafeStreamObserver(StreamObserver super T> streamObserver) {
+ delegate = streamObserver;
+ }
+
+ @Override
+ public void onNext(T t) {
+ if (done) {
+ return;
+ }
+
+ if (t == null) {
+ onError(Status.INVALID_ARGUMENT
+ .withDescription("onNext called with null. Null values are generally not allowed.")
+ .asRuntimeException());
+ } else {
+ try {
+ delegate.onNext(t);
+ } catch (Throwable thrown) {
+ throwIfFatal(thrown);
+ onError(thrown);
+ }
+ }
+ }
+
+ @Override
+ public void onError(Throwable thrown) {
+ try {
+ if (!done) {
+ done = true;
+ delegate.onError(checkNotNull(thrown));
+ } else {
+ LOGGER.log(Level.SEVERE, checkNotNull(thrown), () -> "OnError called after StreamObserver was closed");
+ }
+ } catch (Throwable t) {
+ throwIfFatal(t);
+ LOGGER.log(Level.SEVERE, t, () -> "Caught exception handling onError");
+ }
+ }
+
+ @Override
+ public void onCompleted() {
+ if (done) {
+ LOGGER.log(Level.WARNING, "onComplete called after StreamObserver was closed");
+ } else {
+ try {
+ delegate.onCompleted();
+ } catch (Throwable thrown) {
+ throwIfFatal(thrown);
+ LOGGER.log(Level.SEVERE, thrown, () -> "Caught exception handling onComplete");
+ }
+ }
+ }
+
+ private Throwable checkNotNull(Throwable thrown) {
+ if (thrown == null) {
+ thrown = Status.INVALID_ARGUMENT
+ .withDescription("onError called with null Throwable. Null exceptions are generally not allowed.")
+ .asRuntimeException();
+ }
+
+ return thrown;
+ }
+
+ /**
+ * Throws a particular {@code Throwable} only if it belongs to a set of "fatal" error varieties. These varieties are
+ * as follows:
+ *
+ *
{@code VirtualMachineError}
+ *
{@code ThreadDeath}
+ *
{@code LinkageError}
+ *
+ *
+ * @param thrown the {@code Throwable} to test and perhaps throw
+ */
+ private static void throwIfFatal(Throwable thrown) {
+ if (thrown instanceof VirtualMachineError) {
+ throw (VirtualMachineError) thrown;
+ } else if (thrown instanceof ThreadDeath) {
+ throw (ThreadDeath) thrown;
+ } else if (thrown instanceof LinkageError) {
+ throw (LinkageError) thrown;
+ }
+ }
+
+ /**
+ * Ensure that the specified {@link StreamObserver} is a safe observer.
+ *
+ * If the specified observer is not an instance of {@link SafeStreamObserver} then wrap
+ * it in a {@link SafeStreamObserver}.
+ *
+ * @param observer the {@link StreamObserver} to test
+ * @param the response type expected by the observer
+ *
+ * @return a safe {@link StreamObserver}
+ */
+ public static StreamObserver ensureSafeObserver(StreamObserver observer) {
+ if (observer instanceof SafeStreamObserver) {
+ return observer;
+ }
+
+ return new SafeStreamObserver<>(observer);
+ }
+
+ // ----- constants ------------------------------------------------------
+
+ /**
+ * The {2link Logger} to use.
+ */
+ private static final Logger LOGGER = Logger.getLogger(SafeStreamObserver.class.getName());
+
+ // ----- data members ---------------------------------------------------
+
+ /**
+ * The actual StreamObserver.
+ */
+ private StreamObserver super T> delegate;
+
+ /**
+ * Indicates a terminal state.
+ */
+ private boolean done;
+}
diff --git a/grpc/core/src/main/java/io/helidon/grpc/core/package-info.java b/grpc/core/src/main/java/io/helidon/grpc/core/package-info.java
new file mode 100644
index 00000000000..c7629abba70
--- /dev/null
+++ b/grpc/core/src/main/java/io/helidon/grpc/core/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Core classes used by both the reactive gRPC server API and gRPC client API.
+ */
+package io.helidon.grpc.core;
diff --git a/grpc/core/src/test/java/io/helidon/grpc/core/GrpcHelperTest.java b/grpc/core/src/test/java/io/helidon/grpc/core/GrpcHelperTest.java
new file mode 100644
index 00000000000..136024e324b
--- /dev/null
+++ b/grpc/core/src/test/java/io/helidon/grpc/core/GrpcHelperTest.java
@@ -0,0 +1,666 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.core;
+
+import io.helidon.common.http.Http;
+
+import io.grpc.Status;
+import io.grpc.StatusException;
+import io.grpc.StatusRuntimeException;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * {@link GrpcHelper} unit tests
+ */
+public class GrpcHelperTest {
+
+ @Test
+ public void shouldExtractServiceName() {
+ String fullName = "Foo/1234/Bar";
+
+ assertThat(GrpcHelper.extractServiceName(fullName), is("Foo"));
+ }
+
+ @Test
+ public void shouldExtractMethodName() {
+ String fullName = "Foo/1234/Bar";
+
+ assertThat(GrpcHelper.extractMethodName(fullName), is("Bar"));
+ }
+
+ @Test
+ public void shouldExtractNamePrefix() {
+ String fullName = "Foo/1234/Bar";
+
+ assertThat(GrpcHelper.extractNamePrefix(fullName), is("Foo/1234"));
+ }
+
+ @Test
+ public void shouldConvertAbortedStatusException() {
+ StatusException exception = Status.ABORTED.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Internal Server Error"));
+ }
+
+ @Test
+ public void shouldConvertAbortedStatusExceptionWithDescription() {
+ StatusException exception = Status.ABORTED.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertAlreadyExistsStatusException() {
+ StatusException exception = Status.ALREADY_EXISTS.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(412));
+ assertThat(status.reasonPhrase(), is("Precondition Failed"));
+ }
+
+ @Test
+ public void shouldConvertAlreadyExistsStatusExceptionWithDescription() {
+ StatusException exception = Status.ALREADY_EXISTS.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(412));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertOkStatusException() {
+ StatusException exception = Status.OK.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(200));
+ assertThat(status.reasonPhrase(), is("OK"));
+ }
+
+ @Test
+ public void shouldConvertOkStatusExceptionWithDescription() {
+ StatusException exception = Status.OK.withDescription("Good!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(200));
+ assertThat(status.reasonPhrase(), is("Good!"));
+ }
+
+ @Test
+ public void shouldConvertInvalidArgumentStatusException() {
+ StatusException exception = Status.INVALID_ARGUMENT.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(400));
+ assertThat(status.reasonPhrase(), is("Bad Request"));
+ }
+
+ @Test
+ public void shouldConvertInvalidArgumentStatusExceptionWithDescription() {
+ StatusException exception = Status.INVALID_ARGUMENT.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(400));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertDeadlineExceededStatusException() {
+ StatusException exception = Status.DEADLINE_EXCEEDED.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(408));
+ assertThat(status.reasonPhrase(), is("Request Timeout"));
+ }
+
+ @Test
+ public void shouldConvertDeadlineExceededStatusExceptionWithDescription() {
+ StatusException exception = Status.DEADLINE_EXCEEDED.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(408));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertNotFoundStatusException() {
+ StatusException exception = Status.NOT_FOUND.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(404));
+ assertThat(status.reasonPhrase(), is("Not Found"));
+ }
+
+ @Test
+ public void shouldConvertNotFoundStatusExceptionWithDescription() {
+ StatusException exception = Status.NOT_FOUND.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(404));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertPermissionDeniedStatusException() {
+ StatusException exception = Status.PERMISSION_DENIED.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(403));
+ assertThat(status.reasonPhrase(), is("Forbidden"));
+ }
+
+ @Test
+ public void shouldConvertPermissionDeniedStatusExceptionWithDescription() {
+ StatusException exception = Status.PERMISSION_DENIED.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(403));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertFailedPreconditionStatusException() {
+ StatusException exception = Status.FAILED_PRECONDITION.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(412));
+ assertThat(status.reasonPhrase(), is("Precondition Failed"));
+ }
+
+ @Test
+ public void shouldConvertFailedPreconditionStatusExceptionWithDescription() {
+ StatusException exception = Status.FAILED_PRECONDITION.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(412));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertOutOfRangeStatusException() {
+ StatusException exception = Status.OUT_OF_RANGE.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(400));
+ assertThat(status.reasonPhrase(), is("Bad Request"));
+ }
+
+ @Test
+ public void shouldConvertOutOfRangeStatusExceptionWithDescription() {
+ StatusException exception = Status.OUT_OF_RANGE.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(400));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertUnimplementedStatusException() {
+ StatusException exception = Status.UNIMPLEMENTED.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(501));
+ assertThat(status.reasonPhrase(), is("Not Implemented"));
+ }
+
+ @Test
+ public void shouldConvertUnimplementedStatusExceptionWithDescription() {
+ StatusException exception = Status.UNIMPLEMENTED.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(501));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertUnavailableStatusException() {
+ StatusException exception = Status.UNAVAILABLE.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(503));
+ assertThat(status.reasonPhrase(), is("Service Unavailable"));
+ }
+
+ @Test
+ public void shouldConvertUnavailableStatusExceptionWithDescription() {
+ StatusException exception = Status.UNAVAILABLE.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(503));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertUnauthenticatedStatusException() {
+ StatusException exception = Status.UNAUTHENTICATED.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(401));
+ assertThat(status.reasonPhrase(), is("Unauthorized"));
+ }
+
+ @Test
+ public void shouldConvertUnauthenticatedStatusExceptionWithDescription() {
+ StatusException exception = Status.UNAUTHENTICATED.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(401));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertCancelledStatusException() {
+ StatusException exception = Status.CANCELLED.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Internal Server Error"));
+ }
+
+ @Test
+ public void shouldConvertCancelledStatusExceptionWithDescription() {
+ StatusException exception = Status.CANCELLED.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertDataLossStatusException() {
+ StatusException exception = Status.DATA_LOSS.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Internal Server Error"));
+ }
+
+ @Test
+ public void shouldConvertDataLossStatusExceptionWithDescription() {
+ StatusException exception = Status.DATA_LOSS.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertInternalStatusException() {
+ StatusException exception = Status.INTERNAL.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Internal Server Error"));
+ }
+
+ @Test
+ public void shouldConvertInternalStatusExceptionWithDescription() {
+ StatusException exception = Status.INTERNAL.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertResourceExhaustedStatusException() {
+ StatusException exception = Status.RESOURCE_EXHAUSTED.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Internal Server Error"));
+ }
+
+ @Test
+ public void shouldConvertResourceExhaustedStatusExceptionWithDescription() {
+ StatusException exception = Status.RESOURCE_EXHAUSTED.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertUnknownStatusException() {
+ StatusException exception = Status.UNKNOWN.asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Internal Server Error"));
+ }
+
+ @Test
+ public void shouldConvertUnknownStatusExceptionWithDescription() {
+ StatusException exception = Status.UNKNOWN.withDescription("Oops!").asException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertAbortedStatusRuntimeException() {
+ StatusRuntimeException exception = Status.ABORTED.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Internal Server Error"));
+ }
+
+ @Test
+ public void shouldConvertAbortedStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.ABORTED.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertAlreadyExistsStatusRuntimeException() {
+ StatusRuntimeException exception = Status.ALREADY_EXISTS.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(412));
+ assertThat(status.reasonPhrase(), is("Precondition Failed"));
+ }
+
+ @Test
+ public void shouldConvertAlreadyExistsStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.ALREADY_EXISTS.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(412));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertOkStatusRuntimeException() {
+ StatusRuntimeException exception = Status.OK.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(200));
+ assertThat(status.reasonPhrase(), is("OK"));
+ }
+
+ @Test
+ public void shouldConvertOkStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.OK.withDescription("Good!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(200));
+ assertThat(status.reasonPhrase(), is("Good!"));
+ }
+
+ @Test
+ public void shouldConvertInvalidArgumentStatusRuntimeException() {
+ StatusRuntimeException exception = Status.INVALID_ARGUMENT.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(400));
+ assertThat(status.reasonPhrase(), is("Bad Request"));
+ }
+
+ @Test
+ public void shouldConvertInvalidArgumentStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.INVALID_ARGUMENT.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(400));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertDeadlineExceededStatusRuntimeException() {
+ StatusRuntimeException exception = Status.DEADLINE_EXCEEDED.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(408));
+ assertThat(status.reasonPhrase(), is("Request Timeout"));
+ }
+
+ @Test
+ public void shouldConvertDeadlineExceededStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.DEADLINE_EXCEEDED.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(408));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertNotFoundStatusRuntimeException() {
+ StatusRuntimeException exception = Status.NOT_FOUND.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(404));
+ assertThat(status.reasonPhrase(), is("Not Found"));
+ }
+
+ @Test
+ public void shouldConvertNotFoundStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.NOT_FOUND.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(404));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertPermissionDeniedStatusRuntimeException() {
+ StatusRuntimeException exception = Status.PERMISSION_DENIED.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(403));
+ assertThat(status.reasonPhrase(), is("Forbidden"));
+ }
+
+ @Test
+ public void shouldConvertPermissionDeniedStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.PERMISSION_DENIED.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(403));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertFailedPreconditionStatusRuntimeException() {
+ StatusRuntimeException exception = Status.FAILED_PRECONDITION.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(412));
+ assertThat(status.reasonPhrase(), is("Precondition Failed"));
+ }
+
+ @Test
+ public void shouldConvertFailedPreconditionStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.FAILED_PRECONDITION.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(412));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertOutOfRangeStatusRuntimeException() {
+ StatusRuntimeException exception = Status.OUT_OF_RANGE.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(400));
+ assertThat(status.reasonPhrase(), is("Bad Request"));
+ }
+
+ @Test
+ public void shouldConvertOutOfRangeStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.OUT_OF_RANGE.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(400));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertUnimplementedStatusRuntimeException() {
+ StatusRuntimeException exception = Status.UNIMPLEMENTED.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(501));
+ assertThat(status.reasonPhrase(), is("Not Implemented"));
+ }
+
+ @Test
+ public void shouldConvertUnimplementedStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.UNIMPLEMENTED.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(501));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertUnavailableStatusRuntimeException() {
+ StatusRuntimeException exception = Status.UNAVAILABLE.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(503));
+ assertThat(status.reasonPhrase(), is("Service Unavailable"));
+ }
+
+ @Test
+ public void shouldConvertUnavailableStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.UNAVAILABLE.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(503));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertUnauthenticatedStatusRuntimeException() {
+ StatusRuntimeException exception = Status.UNAUTHENTICATED.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(401));
+ assertThat(status.reasonPhrase(), is("Unauthorized"));
+ }
+
+ @Test
+ public void shouldConvertUnauthenticatedStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.UNAUTHENTICATED.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(401));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertCancelledStatusRuntimeException() {
+ StatusRuntimeException exception = Status.CANCELLED.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Internal Server Error"));
+ }
+
+ @Test
+ public void shouldConvertCancelledStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.CANCELLED.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertDataLossStatusRuntimeException() {
+ StatusRuntimeException exception = Status.DATA_LOSS.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Internal Server Error"));
+ }
+
+ @Test
+ public void shouldConvertDataLossStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.DATA_LOSS.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertInternalStatusRuntimeException() {
+ StatusRuntimeException exception = Status.INTERNAL.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Internal Server Error"));
+ }
+
+ @Test
+ public void shouldConvertInternalStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.INTERNAL.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertResourceExhaustedStatusRuntimeException() {
+ StatusRuntimeException exception = Status.RESOURCE_EXHAUSTED.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Internal Server Error"));
+ }
+
+ @Test
+ public void shouldConvertResourceExhaustedStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.RESOURCE_EXHAUSTED.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+
+ @Test
+ public void shouldConvertUnknownStatusRuntimeException() {
+ StatusRuntimeException exception = Status.UNKNOWN.asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Internal Server Error"));
+ }
+
+ @Test
+ public void shouldConvertUnknownStatusRuntimeExceptionWithDescription() {
+ StatusRuntimeException exception = Status.UNKNOWN.withDescription("Oops!").asRuntimeException();
+ Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
+
+ assertThat(status.code(), is(500));
+ assertThat(status.reasonPhrase(), is("Oops!"));
+ }
+}
diff --git a/grpc/core/src/test/java/io/helidon/grpc/core/PriorityBagTest.java b/grpc/core/src/test/java/io/helidon/grpc/core/PriorityBagTest.java
new file mode 100644
index 00000000000..f6697d0a4be
--- /dev/null
+++ b/grpc/core/src/test/java/io/helidon/grpc/core/PriorityBagTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.grpc.core;
+
+import java.util.Arrays;
+
+import javax.annotation.Priority;
+
+import io.helidon.common.Prioritized;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
+
+
+public class PriorityBagTest {
+
+ @Test
+ public void shouldReturnElementsInOrder() {
+ PriorityBag bag = new PriorityBag<>();
+ bag.add("Three", 3);
+ bag.add("Two", 2);
+ bag.add("One", 1);
+
+ assertThat(bag, contains("One", "Two", "Three"));
+ }
+
+ @Test
+ public void shouldReturnElementsInOrderWithinSamePriority() {
+ PriorityBag bag = new PriorityBag<>();
+ bag.add("Two", 2);
+ bag.add("TwoToo", 2);
+
+ assertThat(bag, contains("Two", "TwoToo"));
+ }
+
+ @Test
+ public void shouldReturnNoPriorityElementsLast() {
+ PriorityBag bag = new PriorityBag<>();
+ bag.add("Three", 3);
+ bag.add("Last");
+ bag.add("One", 1);
+
+ assertThat(bag, contains("One", "Three", "Last"));
+ }
+
+ @Test
+ public void shouldGetPriorityFromAnnotation() {
+ PriorityBag