diff --git a/examples/microprofile/grpc/README.md b/examples/microprofile/grpc/README.md new file mode 100644 index 00000000..4fa6c590 --- /dev/null +++ b/examples/microprofile/grpc/README.md @@ -0,0 +1,15 @@ +# Helidon gRPC MP Example + +This examples shows a simple application written using Helidon gRPC MP API: + +- StringService: a gRPC service implementation that uses MP +- StringServiceClient: an interface from which a client proxy can be created to call StringService remote methods +- StringServiceTest: a sample test that starts a server and tests the client and server components +- application.yaml: configuration for server and client channels + +## Build and run + +```shell +mvn package +java -jar target/helidon-examples-microprofile-grpc.jar +``` \ No newline at end of file diff --git a/examples/microprofile/grpc/pom.xml b/examples/microprofile/grpc/pom.xml new file mode 100644 index 00000000..fcd36e79 --- /dev/null +++ b/examples/microprofile/grpc/pom.xml @@ -0,0 +1,114 @@ + + + + 4.0.0 + + io.helidon.applications + helidon-mp + 4.1.0-SNAPSHOT + + + io.helidon.examples.microprofile + helidon-examples-microprofile-grpc + Helidon Examples Microprofile gRPC + + + Microprofile example that uses gRPC + + + + + io.helidon.grpc + helidon-grpc-core + + + io.helidon.microprofile.grpc + helidon-microprofile-grpc-core + + + io.helidon.microprofile.grpc + helidon-microprofile-grpc-server + + + io.helidon.microprofile.grpc + helidon-microprofile-grpc-client + + + io.helidon.microprofile.testing + helidon-microprofile-testing-junit5 + test + + + io.helidon.webserver.testing.junit5 + helidon-webserver-testing-junit5-grpc + test + + + io.helidon.logging + helidon-logging-jul + runtime + + + + javax.annotation + javax.annotation-api + provided + true + + + + + + + + kr.motd.maven + os-maven-plugin + ${version.plugin.os} + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + + compile + test-compile + + + + + + + \ No newline at end of file diff --git a/examples/microprofile/grpc/src/main/java/io/helidon/examples/microprofile/grpc/StringService.java b/examples/microprofile/grpc/src/main/java/io/helidon/examples/microprofile/grpc/StringService.java new file mode 100644 index 00000000..046793bd --- /dev/null +++ b/examples/microprofile/grpc/src/main/java/io/helidon/examples/microprofile/grpc/StringService.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.microprofile.grpc; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.helidon.grpc.api.Grpc; +import io.helidon.grpc.core.CollectingObserver; + +import io.grpc.stub.StreamObserver; +import jakarta.enterprise.context.ApplicationScoped; + +/** + * An implementation of a string service. + */ +@Grpc.GrpcService +@ApplicationScoped +public class StringService { + + /** + * Uppercase a string. + * + * @param request string message + * @return string message + */ + @Grpc.Unary("Upper") + public Strings.StringMessage upper(Strings.StringMessage request) { + return newMessage(request.getText().toUpperCase()); + } + + /** + * Lowercase a string. + * + * @param request string message + * @return string message + */ + @Grpc.Unary("Lower") + public Strings.StringMessage lower(Strings.StringMessage request) { + return newMessage(request.getText().toLowerCase()); + } + + /** + * Split a string using space delimiters. + * + * @param request string message + * @return stream of string messages + */ + @Grpc.ServerStreaming("Split") + public Stream split(Strings.StringMessage request) { + String[] parts = request.getText().split(" "); + return Stream.of(parts).map(this::newMessage); + } + + /** + * Join a stream of messages using spaces. + * + * @param observer stream of messages + * @return single message as a stream + */ + @Grpc.ClientStreaming("Join") + public StreamObserver join(StreamObserver observer) { + return CollectingObserver.create( + Collectors.joining(" "), + observer, + Strings.StringMessage::getText, + this::newMessage); + } + + private Strings.StringMessage newMessage(String text) { + return Strings.StringMessage.newBuilder().setText(text).build(); + } +} + diff --git a/examples/microprofile/grpc/src/main/java/io/helidon/examples/microprofile/grpc/StringServiceClient.java b/examples/microprofile/grpc/src/main/java/io/helidon/examples/microprofile/grpc/StringServiceClient.java new file mode 100644 index 00000000..b03ee3e9 --- /dev/null +++ b/examples/microprofile/grpc/src/main/java/io/helidon/examples/microprofile/grpc/StringServiceClient.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.examples.microprofile.grpc; + +import java.util.stream.Stream; + +import io.helidon.grpc.api.Grpc; + +import io.grpc.stub.StreamObserver; + +/** + * A client for a {@link StringService}. + */ +@Grpc.GrpcService("StringService") +@Grpc.GrpcChannel("string-channel") // see application.yaml +public interface StringServiceClient { + + /** + * Uppercase a string. + * + * @param request string message + * @return string message + */ + @Grpc.Unary("Upper") + Strings.StringMessage upper(Strings.StringMessage request); + + /** + * Lowercase a string. + * + * @param request string message + * @return string message + */ + @Grpc.Unary("Lower") + Strings.StringMessage lower(Strings.StringMessage request); + + /** + * Split a string using space delimiters. + * + * @param request string message + * @return stream of string messages + */ + @Grpc.ServerStreaming("Split") + Stream split(Strings.StringMessage request); + + /** + * Join a stream of messages using spaces. + * + * @param observer stream of messages + * @return single message as a stream + */ + @Grpc.ClientStreaming("Join") + StreamObserver join(StreamObserver observer); +} + diff --git a/examples/microprofile/grpc/src/main/java/io/helidon/examples/microprofile/grpc/package-info.java b/examples/microprofile/grpc/src/main/java/io/helidon/examples/microprofile/grpc/package-info.java new file mode 100644 index 00000000..fbf691b8 --- /dev/null +++ b/examples/microprofile/grpc/src/main/java/io/helidon/examples/microprofile/grpc/package-info.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.examples.microprofile.grpc; diff --git a/examples/microprofile/grpc/src/main/proto/strings.proto b/examples/microprofile/grpc/src/main/proto/strings.proto new file mode 100644 index 00000000..48c8bd30 --- /dev/null +++ b/examples/microprofile/grpc/src/main/proto/strings.proto @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +syntax = "proto3"; +option java_package = "io.helidon.examples.microprofile.grpc"; + +service StringService { + rpc Upper (StringMessage) returns (StringMessage) {} + rpc Lower (StringMessage) returns (StringMessage) {} + rpc Split (StringMessage) returns (stream StringMessage) {} + rpc Join (stream StringMessage) returns (StringMessage) {} +} + +message StringMessage { + string text = 1; +} diff --git a/examples/microprofile/grpc/src/main/resources/META-INF/beans.xml b/examples/microprofile/grpc/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000..69f490f1 --- /dev/null +++ b/examples/microprofile/grpc/src/main/resources/META-INF/beans.xml @@ -0,0 +1,25 @@ + + + + diff --git a/examples/microprofile/grpc/src/main/resources/application.yaml b/examples/microprofile/grpc/src/main/resources/application.yaml new file mode 100644 index 00000000..fdc488cf --- /dev/null +++ b/examples/microprofile/grpc/src/main/resources/application.yaml @@ -0,0 +1,48 @@ +# +# Copyright (c) 2024 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +server: + port: 0 + tls: + trust: + keystore: + passphrase: "password" + trust-store: true + resource: + resource-path: "server.p12" + private-key: + keystore: + passphrase: "password" + resource: + resource-path: "server.p12" + +grpc: + client: + channels: + - name: "string-channel" + port: 0 + tls: + trust: + keystore: + passphrase: "password" + trust-store: true + resource: + resource-path: "client.p12" + private-key: + keystore: + passphrase: "password" + resource: + resource-path: "client.p12" \ No newline at end of file diff --git a/examples/microprofile/grpc/src/main/resources/client.p12 b/examples/microprofile/grpc/src/main/resources/client.p12 new file mode 100644 index 00000000..4eb3b832 Binary files /dev/null and b/examples/microprofile/grpc/src/main/resources/client.p12 differ diff --git a/examples/microprofile/grpc/src/main/resources/logging.properties b/examples/microprofile/grpc/src/main/resources/logging.properties new file mode 100644 index 00000000..10379e42 --- /dev/null +++ b/examples/microprofile/grpc/src/main/resources/logging.properties @@ -0,0 +1,29 @@ +# +# Copyright (c) 2019, 2024 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Example Logging Configuration File +# For more information see $JAVA_HOME/jre/lib/logging.properties + +# Send messages to the console +handlers=io.helidon.logging.jul.HelidonConsoleHandler + +# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n + +# Global logging level. Can be overridden by specific loggers +.level=INFO + + diff --git a/examples/microprofile/grpc/src/main/resources/server.p12 b/examples/microprofile/grpc/src/main/resources/server.p12 new file mode 100644 index 00000000..ff8e4ddf Binary files /dev/null and b/examples/microprofile/grpc/src/main/resources/server.p12 differ diff --git a/examples/microprofile/grpc/src/test/java/io/helidon/examples/microprofile/grpc/StringServiceTest.java b/examples/microprofile/grpc/src/test/java/io/helidon/examples/microprofile/grpc/StringServiceTest.java new file mode 100644 index 00000000..b6e9410f --- /dev/null +++ b/examples/microprofile/grpc/src/test/java/io/helidon/examples/microprofile/grpc/StringServiceTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.examples.microprofile.grpc; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +import io.helidon.grpc.api.Grpc; +import io.helidon.microprofile.grpc.client.GrpcConfigurablePort; +import io.helidon.microprofile.testing.junit5.HelidonTest; + +import io.grpc.stub.StreamObserver; +import jakarta.inject.Inject; +import jakarta.ws.rs.client.WebTarget; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasSize; + +@HelidonTest +class StringServiceTest { + + @Inject + private WebTarget webTarget; + + @Inject + @Grpc.GrpcProxy + private StringServiceClient client; + + @BeforeEach + void updatePort() { + if (client instanceof GrpcConfigurablePort c) { + c.channelPort(webTarget.getUri().getPort()); + } + } + + @Test + void testUnaryUpper() { + Strings.StringMessage res = client.upper(newMessage("hello")); + assertThat(res.getText(), is("HELLO")); + } + + @Test + void testUnaryLower() { + Strings.StringMessage res = client.lower(newMessage("HELLO")); + assertThat(res.getText(), is("hello")); + } + + @Test + void testServerStreamingSplit() { + Stream stream = client.split(newMessage("hello world")); + List value = stream.toList(); + assertThat(value, hasSize(2)); + assertThat(value, contains(newMessage("hello"), newMessage("world"))); + } + + @Test + void testClientStreamingJoin() throws InterruptedException { + ListObserver response = new ListObserver<>(); + StreamObserver request = client.join(response); + request.onNext(newMessage("hello")); + request.onNext(newMessage("world")); + request.onCompleted(); + List value = response.value(); + assertThat(value.getFirst(), is(newMessage("hello world"))); + } + + /** + * Helper method to create a string message from a string. + * + * @param data the string + * @return the string message + */ + Strings.StringMessage newMessage(String data) { + return Strings.StringMessage.newBuilder().setText(data).build(); + } + + /** + * Helper class to collect a list of observed values. + * + * @param the type of values + */ + static class ListObserver implements StreamObserver { + private static final long TIMEOUT_SECONDS = 10; + + private List value = new ArrayList<>(); + private final CountDownLatch latch = new CountDownLatch(1); + + public List value() throws InterruptedException { + boolean b = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assert b; + return value; + } + + @Override + public void onNext(T value) { + this.value.add(value); + } + + @Override + public void onError(Throwable t) { + value = null; + } + + @Override + public void onCompleted() { + latch.countDown(); + } + } +} + diff --git a/examples/microprofile/pom.xml b/examples/microprofile/pom.xml index db26994f..fd4c017e 100644 --- a/examples/microprofile/pom.xml +++ b/examples/microprofile/pom.xml @@ -52,6 +52,7 @@ http-status-count-mp lra telemetry + grpc threads