diff --git a/content/docs/grpc/grpc-bridge.adoc b/content/docs/grpc/grpc-bridge.adoc index e027802..ef92cd4 100644 --- a/content/docs/grpc/grpc-bridge.adoc +++ b/content/docs/grpc/grpc-bridge.adoc @@ -4,8 +4,8 @@ layout: default link: /docs/grpc --- -The RESTEasy *gRPC Bridge Project* (aka *resteasy-grpc*) project -(https://github.com/resteasy/resteasy-grpc) has been developed to +The RESTEasy *gRPC Bridge Project* (aka *resteasy-grpc*) +(https://github.com/resteasy/resteasy-grpc ) has been developed to enable communication between gRPC clients and Jakarta REST servers. The classes in resteasy-grpc's *grpc-bridge* module are able to scan a directory tree of Jakarta REST resource classes and generate classes @@ -13,7 +13,7 @@ that form an intermediary layer between the gRPC runtime and a Jakarta REST server. Those generated classes are supported by the *grpc-bridge-runtime* module. The process of generating the files is facilitated by the maven archetype -https://github.com/resteasy/gRPCtoJakartaREST-archetype. +https://github.com/resteasy/gRPCtoJakartaREST-archetype . == protobuf and gRPC @@ -36,14 +36,13 @@ defined wire format. For example, [source,protobuf] ---- - syntax = "proto3"; - package org.greet; - option java_package = "org.greet"; - option java_outer_classname = "Greet_proto"; - - message Greeting { - string s = 1; - } +syntax = "proto3"; +package org.greet; +option java_package = "org.greet"; +option java_outer_classname = "Greet_proto"; + message Greeting { + string s = 1; +} ---- is a protobuf IDL file that defines a data type called `Greeting` with a @@ -54,35 +53,30 @@ the output is a several hundred line class called `org.greet.Greet_proto`: [source,java] ---- - package org.greet; - - public final class Greet_proto { - - public static final class Greeting extends - com.google.protobuf.GeneratedMessageV3 implements GreetingOrBuilder { - - public java.lang.String getS() { - ... - } - - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - org.jboss.greeting.Greeting_proto.GreetingOrBuilder { - - public Builder setS() { - ... - } - } - } - } +package org.greet; + public final class Greet_proto { + public static final class Greeting extends + com.google.protobuf.GeneratedMessageV3 implements GreetingOrBuilder { + public java.lang.String getS() { + ... + } + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + org.jboss.greeting.Greeting_proto.GreetingOrBuilder { + public Builder setS() { + ... + } + } + } +} ---- The `Builder` class supports creating a `Greeting` [source,java] ---- - org.greet.Greet_proto.Greeting.Builder builder = org.greet.Greet_proto.Greeting.newBuilder(); - Greeting greeting = builder.setS("foo").build(); +org.greet.Greet_proto.Greeting.Builder builder = org.greet.Greet_proto.Greeting.newBuilder(); +Greeting greeting = builder.setS("foo").build(); ---- and the `getS()` method supports retrieving the contents of the `Greeting`. @@ -92,20 +86,17 @@ example, [source,protobuf] ---- - syntax = "proto3"; - package org.greet; - option java_package = "org.greet"; - option java_outer_classname = "Greet_proto"; - - message Greeting { - string s = 1; - } - - message gString {string value = 2;} - - service GreetService { - rpc greet (gString) returns (Greeting); - } +syntax = "proto3"; +package org.greet; +option java_package = "org.greet"; +option java_outer_classname = "Greet_proto"; + message Greeting { + string s = 1; +} + message gString {string value = 1;} + service GreetService { + rpc greet (gString) returns (Greeting); +} ---- adds the `greet` remote call. Note that it also adds a `gString` type @@ -116,25 +107,25 @@ addition to `org.greet.Greet_proto`, is the class [source,java] ---- - public final class GreetServiceGrpc { - ... - public static final class GreetServiceBlockingStub extends - io.grpc.stub.AbstractBlockingStub { - ... - public org.greet.Greet_proto.Greeting greet(org.greet.Greet_proto.gString request) { - ... - } - } - ... - public static final class GreetServiceStub extends - io.grpc.stub.AbstractAsyncStub { - ... - public void greet(org.greet.Greet_proto.gString request, - io.grpc.stub.StreamObserver responseObserver) { - ... - } - } - } +public final class GreetServiceGrpc { + ... + public static final class GreetServiceBlockingStub extends + io.grpc.stub.AbstractBlockingStub { + ... + public org.greet.Greet_proto.Greeting greet(org.greet.Greet_proto.gString request) { + ... + } + } + ... + public static final class GreetServiceStub extends + io.grpc.stub.AbstractAsyncStub { + ... + public void greet(org.greet.Greet_proto.gString request, + io.grpc.stub.StreamObserver responseObserver) { + ... + } + } +} ---- === Client stubs @@ -146,21 +137,19 @@ something like: [source,java] ---- - private static String target = "localhost:8082"; - private static ManagedChannel channel; - private static GreetServiceBlockingStub blockingStub; - - public static void setup() throws Exception { - channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build(); - blockingStub = GreetServiceGrpc.newBlockingStub(channel); - } - - public void test() throws Exception { - org.greet.Greet_proto.gString gs = org.greet.Greet_proto.gString.newBuilder().setS("foo").build(); - org.greet.Greet_proto.Greeting response = blockingStub.greet(gs); - String s = response.getS(); - ... - } +private static String target = "localhost:8082"; +private static ManagedChannel channel; +private static GreetServiceBlockingStub blockingStub; + public static void setup() throws Exception { + channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build(); + blockingStub = GreetServiceGrpc.newBlockingStub(channel); +} + public void test() throws Exception { + org.greet.Greet_proto.gString gs = org.greet.Greet_proto.gString.newBuilder().setS("foo").build(); + org.greet.Greet_proto.Greeting response = blockingStub.greet(gs); + String s = response.getS(); + ... +} ---- There are also @@ -174,17 +163,17 @@ The gRPC plugin also generates the inner class `GreetServiceGrpc.GreetServiceImplBase`, which has a default method for each rpc entry in the IDL file. The default method will indicate that the method is not implemented. The idea is that the developer should -create a class extending `GreetServiceImplBase` with implementing -methods. A simple example is +create a class extending `GreetServiceImplBase` with implementing methods. +A simple example is [source,java] ---- - @Override - public void greet(org.greet.Greet_proto.gString request, StreamObserver responseObserver) { - String name = request.getValue(); - org.greet.Greet_proto.Greeting greeting = org.greet.Greet_proto.Greeting.newBuilder().setS("hello, " + name).build(); - responseObserver.onNext(greeting); - } +@Override +public void greet(org.greet.Greet_proto.gString request, StreamObserver responseObserver) { + String name = request.getValue(); + org.greet.Greet_proto.Greeting greeting = org.greet.Greet_proto.Greeting.newBuilder().setS("hello, " + name).build(); + responseObserver.onNext(greeting); +} ---- === google.protobuf.Any @@ -196,37 +185,37 @@ The definition of `Any` is [source,protobuf] ---- - message Any { - string type_url = 1; - bytes value = 2; - } +message Any { + string type_url = 1; + bytes value = 2; +} ---- The `value` field has built-in type `bytes`, which "May contain any arbitrary sequence of bytes no longer than 2^32", according to -https://developers.google.com/protocol-buffers/docs/proto3. The type +https://developers.google.com/protocol-buffers/docs/proto3 . The type of the message stored in the `value` is described by the URL in the `type_url` field. Consider, for example, [source,java] ---- - gString gs = gString.newBuilder().setValue("abc").build(); - Message m = Any.pack(gs); - System.out.println(m); +gString gs = gString.newBuilder().setValue("abc").build(); +Message m = Any.pack(gs); +System.out.println(m); ---- The output is [source,protobuf] ---- - type_url: "type.googleapis.com/org.greet.gString" - value: "\272\001\003abc" +type_url: "type.googleapis.com/org.greet.gString" +value: "\272\001\003abc" ---- The string "\272\001\003abc" is the internal representation of a `gString`, the details of which are beyond the scope of this discussion. -See https://developers.google.com/protocol-buffers/docs/encoding for -details. The URL is "type.googleapis.com/org.greet.gString", where the +See https://developers.google.com/protocol-buffers/docs/encoding +for details. The URL is "type.googleapis.com/org.greet.gString", where the path "org.greet.gString" gives the type of the object represented in the `value` field. @@ -235,29 +224,29 @@ the value of the `Any`. Consider, for example, the code [source,java] ---- - Any any = null; - if (/* some predicate */) { - gString gs = gString.newBuilder().setValue("abc").build(); - any = Any.pack(gs); - } else { - gInteger gi = gInteger.newBuilder().setValue(7).build(); - any = Any.pack(gi); - } - /* send any */ +Any any = null; +if (/* some predicate */) { + gString gs = gString.newBuilder().setValue("abc").build(); + any = Any.pack(gs); +} else { + gInteger gi = gInteger.newBuilder().setValue(7).build(); + any = Any.pack(gi); +} +/* send any */ ---- Then, the `Any` can be unpacked as follows: [source,java] ---- - /* get any */ - if (any.getTypeUrl().endsWith("org.greet.gString")) { - gString gs = any.unpack(gString.class); - System.out.println("gs: " + gs); - } else if (any.getTypeUrl().endsWith("org.greet.gInteger")) { - gInteger gi = any.unpack(gInteger.class); - System.out.println("gi: " + gi); - } +/* get any */ +if (any.getTypeUrl().endsWith("org.greet.gString")) { + gString gs = any.unpack(gString.class); + System.out.println("gs: " + gs); +} else if (any.getTypeUrl().endsWith("org.greet.gInteger")) { + gInteger gi = any.unpack(gInteger.class); + System.out.println("gi: " + gi); +} ---- == Connecting a gRPC client to a Jakarta REST server: Semantic issues @@ -278,7 +267,7 @@ resteasy-grpc's grpc-bridge module automates the process. The class `dev.resteasy.grpc.bridge.generator.protobuf.JavaToProtobufGenerator` traverses, with the help of the Java parser -https://github.com/javaparser/javaparser, a set of Jakarta REST +https://github.com/javaparser/javaparser , a set of Jakarta REST resource classes. For each class that appears as an entity type or a return type of a resource method or resource locator, `JavaToProtobufGenerator` generates a protobuf message. For each @@ -295,136 +284,118 @@ Given `org.greet.Greeting` [source,java] ---- - package org.greet; - - public class Greeting { - private String s; - - public Greeting(String s) { - this.s = s; - } - } +package org.greet; + public class Greeting { + private String s; + public Greeting(String s) { + this.s = s; + } +} ---- and `org.greet.Greeter` [source,java] ---- - package org.greet; - - import jakarta.ws.rs.GET; - import jakarta.ws.rs.Path; - - @Path("") - public class Greeter { - - @GET - @Path("greet") - public Greeting greet(String s) { - return new Greeting("hello, " + s); - } - } +package org.greet; + import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + @Path("") +public class Greeter { + @GET + @Path("greet") + public Greeting greet(String s) { + return new Greeting("hello, " + s); + } +} ---- `JavaToProtobufGenerator` will generate the IDL file Greet.proto: [source,protobuf] ---- - syntax = "proto3"; - package org.greet; - import "google/protobuf/any.proto"; - import "google/protobuf/timestamp.proto"; - option java_package = "org.greet"; - option java_outer_classname = "Greet_proto"; - - service GreetService { - // /greet gString org_greet___Greeting GET sync - rpc greet (GeneralEntityMessage) returns (GeneralReturnMessage); - } - - // Type: dev.resteasy.example.grpc.greet.Greeting - message org_greet___Greeting { - string s = 1; - } - - message gInteger {int32 value = 1;} - message gFloat {float value = 1;} - message gCharacter {string value = 1;} - message gByte {int32 value = 1;} - message gLong {int64 value = 1;} - message gString {string value = 1;} - message gBoolean {bool value = 1;} - message gDouble {double value = 1;} - message gShort {int32 value = 1;} - - message gHeader { - repeated string values = 1; - } - - message gCookie { - string name = 1; - string value = 2; - int32 version = 3; - string path = 4; - string domain = 5; - } - - message gNewCookie { - string name = 1; - string value = 2; - int32 version = 3; - string path = 4; - string domain = 5; - string comment = 6; - int32 maxAge = 7; - google.protobuf.Timestamp expiry = 8; - bool secure = 9; - bool httpOnly = 10; - - enum SameSite { - NONE = 0; - LAX = 1; - STRICT = 2; - } - - SameSite sameSite = 11; - } - - message ServletInfo { - string characterEncoding = 1; - string clientAddress = 2; - string clientHost = 3; - int32 clientPort = 4; - } - - message FormValues { - repeated string formValues_field = 1; - } - - message FormMap { - map formMap_field = 1; - } - - message GeneralEntityMessage { - ServletInfo servletInfo = 1; - string URL = 2; - map headers = 3; - repeated gCookie cookies = 4; - string httpMethod = 5; - oneof messageType { - gString gString_field = 6; - FormMap form_field = 7; - } - } - - message GeneralReturnMessage { - map headers = 1; - repeated gNewCookie cookies = 2; - gInteger status = 3; - oneof messageType { - org_greet___Greeting org_greet___Greeting_field = 4; - } - } +syntax = "proto3"; +package org.greet; +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; +option java_package = "org.greet"; +option java_outer_classname = "Greet_proto"; + service GreetService { +// /greet gString org_greet___Greeting GET sync + rpc greet (GeneralEntityMessage) returns (GeneralReturnMessage); +} + // Type: dev.resteasy.example.grpc.greet.Greeting +message org_greet___Greeting { + string s = 1; +} + message gInteger {int32 value = 1;} +message gFloat {float value = 1;} +message gCharacter {string value = 1;} +message gByte {int32 value = 1;} +message gLong {int64 value = 1;} +message gString {string value = 1;} +message gBoolean {bool value = 1;} +message gDouble {double value = 1;} +message gShort {int32 value = 1;} + message gHeader { + repeated string values = 1; +} + message gCookie { + string name = 1; + string value = 2; + int32 version = 3; + string path = 4; + string domain = 5; +} + message gNewCookie { + string name = 1; + string value = 2; + int32 version = 3; + string path = 4; + string domain = 5; + string comment = 6; + int32 maxAge = 7; + google.protobuf.Timestamp expiry = 8; + bool secure = 9; + bool httpOnly = 10; + enum SameSite { + NONE = 0; + LAX = 1; + STRICT = 2; + } + SameSite sameSite = 11; +} + message ServletInfo { + string characterEncoding = 1; + string clientAddress = 2; + string clientHost = 3; + int32 clientPort = 4; +} + message FormValues { + repeated string formValues_field = 1; +} + message FormMap { + map formMap_field = 1; +} + message GeneralEntityMessage { + ServletInfo servletInfo = 1; + string URL = 2; + map headers = 3; + repeated gCookie cookies = 4; + string httpMethod = 5; + oneof messageType { + gString gString_field = 6; + FormMap form_field = 7; + } +} + message GeneralReturnMessage { + map headers = 1; + repeated gNewCookie cookies = 2; + gInteger status = 3; + oneof messageType { + org_greet___Greeting org_greet___Greeting_field = 4; + } +} ---- Clearly, the generated IDL file is more complicated than the one @@ -453,8 +424,8 @@ can accomodate all of those. Also, consider the element [source,protobuf] ---- oneof messageType { - gString gString_field = 5; - FormMap form_field = 6; + gString gString_field = 5; + FormMap form_field = 6; } ---- @@ -473,10 +444,10 @@ Then the `oneof` field would look like [source,protobuf] ---- - oneof messageType { - gString gString_field = 5; - gFloat gFloat_field = 6; - FormMap form_field = 7; +oneof messageType { + gString gString_field = 5; + gFloat gFloat_field = 6; + FormMap form_field = 7; } ---- @@ -509,36 +480,32 @@ accumulating the fields in a single class. Let's define the class [source,java] ---- - package org.greet; - - public class GeneralGreeting extends Greeting { - private String salute; - - public GeneralGreeting(String salute, String s) { - super(s); - this.salute = salute; - } - } +package org.greet; + public class GeneralGreeting extends Greeting { + private String salute; + public GeneralGreeting(String salute, String s) { + super(s); + this.salute = salute; + } +} ---- and extend `Greeter`: [source,java] ---- - @Path("") - public class Greeter { - ... - - @GET - @Path("salute") - public GeneralGreeting generalGreet(@QueryParam("salute") String salute, String s) { - return getGeneralGreeting(salute, s); - } - - private GeneralGreeting getGeneralGreeting(String salute, String name) { - return new GeneralGreeting(salute, name); - } +@Path("") +public class Greeter { + ... + @GET + @Path("salute") + public GeneralGreeting generalGreet(@QueryParam("salute") String salute, String s) { + return getGeneralGreeting(salute, s); } + private GeneralGreeting getGeneralGreeting(String salute, String name) { + return new GeneralGreeting(salute, name); + } +} ---- Then `JavaToProtobufGenerator` will make the following adjustments to @@ -546,36 +513,34 @@ Greet.proto: [source,protobuf] ---- - ... - service GreetService { - // /greet gString org_greet___Greeting GET sync - rpc greet (GeneralEntityMessage) returns (GeneralReturnMessage); - - // /salute gString org_greet___GeneralGreeting GET sync - rpc generalGreet (GeneralEntityMessage) returns (GeneralReturnMessage); // 1 - } - ... - - // Type: dev.resteasy.example.grpc.greet.Greeting - message dev_resteasy_example_grpc_greet___Greeting { - string s = 1; - } +... +service GreetService { +// /greet gString org_greet___Greeting GET sync + rpc greet (GeneralEntityMessage) returns (GeneralReturnMessage); + // /salute gString org_greet___GeneralGreeting GET sync + rpc generalGreet (GeneralEntityMessage) returns (GeneralReturnMessage); // 1 +} +... - // Type: dev.resteasy.example.grpc.greet.GeneralGreeting - message dev_resteasy_example_grpc_greet___GeneralGreeting { // 2 - string s = 1; - string salute = 2; - } - ... - message GeneralReturnMessage { - map headers = 1; - repeated gNewCookie cookies = 2; - int32 status = 3; - oneof messageType { // 3 - org_greet___Greeting org_greet___Greeting_field = 4; - org_greet___GeneralGreeting org_greet___GeneralGreeting_field = 5; - } - } +// Type: dev.resteasy.example.grpc.greet.Greeting +message dev_resteasy_example_grpc_greet___Greeting { + string s = 1; +} + // Type: dev.resteasy.example.grpc.greet.GeneralGreeting +message dev_resteasy_example_grpc_greet___GeneralGreeting { // 2 + string s = 1; + string salute = 2; +} +... +message GeneralReturnMessage { + map headers = 1; + repeated gNewCookie cookies = 2; + int32 status = 3; + oneof messageType { // 3 + org_greet___Greeting org_greet___Greeting_field = 4; + org_greet___GeneralGreeting org_greet___GeneralGreeting_field = 5; + } +} ---- Note the following: @@ -589,51 +554,49 @@ lacking in Jakarta REST annotations, it's not a resource method. === Generic types -Another semantic gap is the lack of *type variables* and *generic types*. resteasy-grp -uses two devices to bridge the gap. +Another semantic gap is the lack of *wildcards*, *type variables*, and *generic types*. +We will call a generic type with one or more wildcards or type variables an *open type*, and +a generic type with no wildcards or type variables a *closed type*. resteasy-grpc +uses two devices to bridge the gap: -. Type wildcards and open type variables are replaced by `java.lang.Object` -. Each generic type is associated with a distinct protobuf message definition. +. Every open type is mapped to a *normalized* closed type which will represent it in the + protobuf world. +. Each normalized type is associated with a distinct protobuf message definition. -For example, consider +Unbounded wildcards and type variables are mapped to `java.lang.Object`. For example, consider [source,java] ---- - package x.y; - - @GET - @Path("grimble/raw") - public void gr_raw(Grimble g1) { - } - - @GET - @Path("grimble/wildcard") - public void gr_wildcard(Grimble g1) { - } - - @GET - @Path("grimble/variable") - public void gr_variable(Grimble g1) { - } - - @GET - @Path("grimble/string") - public void gr_string(Grimble g1) { - } - - @GET - @Path("grimble/integer") - public void gr_integer(Grimble g1) { - } +package x.y; + @GET +@Path("grimble/raw") +public void gr_raw(Grimble g1) { +} + @GET +@Path("grimble/wildcard") +public void gr_wildcard(Grimble g1) { +} + @GET +@Path("grimble/variable") +public void gr_variable(Grimble g1) { +} + @GET +@Path("grimble/string") +public void gr_string(Grimble g1) { +} + @GET +@Path("grimble/integer") +public void gr_integer(Grimble g1) { +} ---- where `x.y.Grimble` is [source,java] ---- - public class Grimble { - T t; - } +public class Grimble { + T t; +} ---- This leads to the following elements in the .proto file: @@ -682,22 +645,45 @@ and `x.y.Grimble`. . The comments on the rpc definitions of `gr_wildcard()` and `gr_variable()` indicate that both take input parameters `x_y_Grimble18`, which is the generated protobuf representation of `x.y.Grimble`. This convergence follows -from the fact that the wildcard and the type variable are both represented by -`java.lang.Object`. +from the fact that `x.y.Grimble` and `x.y.Grimble` are both normalized to +`x.y.Grimble`. . The definition of `x_y___Grimble18`, which represents `x.y.Grimble`, has a single element of type `google.protobuf.Any`, which, as discussed above, represents an arbitrary type, which makes it an appropriate translation of `java.lang.Object`. -=== Lists and sets +In most cases, bounded wildcards and type variables are also mapped to `java.lang.Object`. The +one exception is a generic class with an upper bounded type variable. For example, + +---- +public class TestClass { ... } + +public class BoundedClass { ... } +---- + +turns into something like + +---- +// Type: x.y.BoundedClass +message x_y___BoundedClass17 { + x_y___TestClass t = 1; +} +---- + +*Note:* The definition of `BoundedClass` requires that the variable be replaced by a subclass of +`TestClass`. `BoundedClass` would be rejected by the compiler. + +=== Collections and maps Given their fundamental usefulness, resteasy-grpc gives special attention to -implementations of `java.util.List` and `java.util.Set`. However, rather than +implementations of `java.util.List`, `java.util.Set`, `java.util.Map`, +and `jakarta.ws.rs.core.MultivaluedMap`. However, rather than attempt to support all idiosyncratic features of arbitrary implementations, resteasy-grpc treats them in a simplified manner. In particular, an implementation -of a `List` is considered to be an ordered sequence of elements, and an +of a `List` is considered to be an ordered sequence of elements, an implementation of a `Set` is considered to be an unordered collection of -elements. One reason for this simplification is the inherent complexity of +elements, and `Maps` and `MultivaluedMap` are sets of pairs. +One reason for this simplification is the inherent complexity of some implementations. For example, `java.util.HashMap`, which is used in the standard implementation of `java.util.HashSet`, has the non-static inner class `KeySet`, but non-static inner classes are not currently supported by @@ -758,41 +744,35 @@ message java_util___List30 { ---- Everything discussed in the section about generic types applies to variants of -`List` and `Set`. For example, - -[source,java] +`List`, `Set`, `Map`, and `MultivaluedMap`. For example, ---- - package x.y; - - @Path("arraylist/variable") - @POST - public ArrayList arrayListTest1(ArrayList l) { - return l; - } +package x.y; - @Path("arraylist/wildcard") - @POST - public ArrayList arrayListTest2(ArrayList l) { - return l; - } - - @Path("arraylist/string") - @POST - public ArrayList arrayListTest3(ArrayList l) { - return l; - } - - @Path("arraylist/object") - @POST - public ArrayList arrayListTest4(ArrayList l) { - return l; - } - - @Path("arraylist/notype") - @POST - public ArrayList arrayListTest5(ArrayList l) { - return l; - } +@Path("arraylist/variable") +@POST +public ArrayList arrayListTest1(ArrayList l) { + return l; +} + @Path("arraylist/wildcard") +@POST +public ArrayList arrayListTest2(ArrayList l) { + return l; +} + @Path("arraylist/string") +@POST +public ArrayList arrayListTest3(ArrayList l) { + return l; +} + @Path("arraylist/object") +@POST +public ArrayList arrayListTest4(ArrayList l) { + return l; +} + @Path("arraylist/notype") +@POST +public ArrayList arrayListTest5(ArrayList l) { + return l; +} ---- turns into @@ -835,10 +815,193 @@ message java_util___ArrayList { } ---- +=== Records + +Given that Java records are a specialized kind of type, it's not surprising that resteasy-grpc +handles records. In fact, any semantic construct applicable to records and supported for classes, +e.g., generic types, is also supported for records. + +However, since they have specialized semantics, records are handled somewhat differently internally. +As noted above, protobuf messages derived from classes other than collections and maps are +labeled as Types, as in, for example, +---- +// Type: dev.resteasy.example.grpc.greet.Greeting +message org_greet___Greeting { + string s = 1; +} +---- +In order to distinguish between arbitrary classes and records, records are labeled differently. For example, + +---- +public record Person(String name) { +} +---- + +would be represented as + +---- +// Record: dev.resteasy.grpc.example.Person +message dev_resteasy_grpc_example___Person { + string name = 1; +} +---- + === Arrays -resteasy-grpc has a treatment for arbitrarily nested arrays, -but it is currently under reconsideration. Stay tuned for further discussion. +Protobuf supports simple arrays with the keyword "repeated". For example, + +---- +message intArray { + repeated sfixed32 int_field = 1; +} +---- + +represents a message with an array of integers, i.e., `int[]` in Java. However, there is no +built-in support for multidimensional arrays like `int[][]`, so we have to implement +support explicitly. + +In fact, the treatment of arrays faces two challenges: + +. multidimensional arrays, and +. arrays with null elements. + +We undertake the multidimensional challenge with a provisional protobuf definition of `dev_resteasy_grpc_arrays___ArrayHolder`: + +---- +message dev_resteasy_grpc_arrays___Any___Array { + repeated dev_resteasy_grpc_arrays___Any any_field = 1; +} +... +message dev_resteasy_grpc_arrays___Boolean___Array { + repeated bool boolean_field = 1; +} +... +message dev_resteasy_grpc_arrays___Integer___Array { + repeated sfixed32 int_field = 1; +} +... +message dev_resteasy_grpc_arrays___ArrayHolder___Array { + repeated dev_resteasy_grpc_arrays___ArrayHolder arrayHolder_field = 1; +} +... +message dev_resteasy_grpc_arrays___ArrayHolder { + oneof messageType { + dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___Any___Array dev_resteasy_grpc_arrays___Any___Array_field = 1; + dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___Boolean___Array dev_resteasy_grpc_arrays___Boolean___Array_field = 2; + ... + dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___Integer___Array dev_resteasy_grpc_arrays___Integer___Array_field = 12; +... + dev_resteasy_grpc_arrays___ArrayHolder___Array dev_resteasy_grpc_arrays___ArrayHolder___Array_field = 21; +... +} +---- + +It has + * an array definition for each primitive Java type, e.g. `+dev_resteasy_grpc_arrays___Boolean___Array+` +* an array definition of arbitrary objects: `+dev_resteasy_grpc_arrays___Any___Array_field+` +* a recursive field of `+dev_resteasy_grpc_arrays___ArrayHolder+`'s + +Now, consider + +---- +public class ArrayStuff { + ... + int[] is; + int[][] iss; +} +---- + +That can be represented in protobuf as + +---- +message dev_resteasy_grpc_arrays___ArrayStuff { + ... + repeated int32 is___1 = 1; + dev_resteasy_grpc_arrays___ArrayHolder___Array iss = 2; + ... +} +---- + +That is, `int[][]` is represented as `+dev_resteasy_grpc_arrays___ArrayHolder___Array+`. + +*Note.* The arrays.proto file, included by gRPCtoJakartaREST-archetype, has all of the non primitive +array definitions. + +Now, consider the array `Integer[]` compared to `int[]`. An instance of the latter could be defined + +---- + int[] is = new int[] {3, 5, 7}; +---- + +but + +---- + int[] is = new int[] {3, null, 7}; +---- + +is syntactically incorrect. On the other hand, + +---- + Integer[] Is = new Integer[] {3, null, 7}; +---- + +is just fine. The point is that an array of any non primitive types can hold nulls, +which is another semantic difference between Java and protobuf. One solution is to +define, for each non primitve type, a "nullable" variant; for example, + +---- +message dev_resteasy_grpc_arrays___NONE { + bool boolean_field = 1; +} + +message dev_resteasy_grpc_arrays___Boolean___wrapper { + oneof type { + dev_resteasy_grpc_arrays___NONE none_field = 1; + bool boolean_field = 2; + } +} + +message dev_resteasy_grpc_arrays___Boolean___WArray { + repeated dev_resteasy_grpc_arrays___Boolean___wrapper wrapper_field = 1; +} +---- + +Here, `+dev_resteasy_grpc_arrays___Boolean___wrapper+` can be either a +`+dev_resteasy_grpc_arrays___NONE+`, +which represents a null value, or a `bool`, and the *wrapper array* +`+dev_resteasy_grpc_arrays___Boolean___WArray+` +is a variant of `+dev_resteasy_grpc_arrays___Boolean___Array+` except it can hold null +values. + + +Now, the provisional treatment defined earlier can be updated with the use of wrapper arrays. +For example, + +---- +message dev_resteasy_grpc_arrays___ArrayHolder___wrapper { + oneof type { + dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___NONE none_field = 1; + dev_resteasy_grpc_arrays___ArrayHolder dev_resteasy_grpc_arrays___ArrayHolder_field = 2; + } +} + +message dev_resteasy_grpc_arrays___ArrayHolder___WArray { + string componentType = 1; + repeated dev_resteasy_grpc_arrays___ArrayHolder___wrapper wrapper___field = 2; +} + +message dev_resteasy_grpc_arrays___ArrayHolder { + oneof messageType { + dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___Any___WArray dev_resteasy_grpc_arrays___Any___WArray_field = 1; + dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___Boolean___Array dev_resteasy_grpc_arrays___Boolean___Array_field = 2; + dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___Boolean___WArray dev_resteasy_grpc_arrays___Boolean___WArray_field = 3; + ... + dev_resteasy_grpc_arrays___ArrayHolder___WArray dev_resteasy_grpc_arrays___ArrayHolder___WArray_field = 21; + ... + } +---- + +*Note*. arrays.proto also includes wrapper arrays for primitive types. === Other uses of `google.protobuf.Any` @@ -848,11 +1011,11 @@ Consider [source,java] ---- - @GET - @Path("list/string") - List listTest(List listTest(List l) { + ... +} ---- Given that actual types of the entity `l` or the return value cannot be determined until runtime, @@ -870,22 +1033,22 @@ Consider the resource method [source,java] ---- - public Response m() { - if (test()) { - return Response.ok(new X()).build(); - } else { - return Response.ok(new Y()).build(); - } - } +public Response m() { + if (test()) { + return Response.ok(new X()).build(); + } else { + return Response.ok(new Y()).build(); + } +} ---- Will it return an `X` or a `Y`? If `test()` is [source,java] ---- - public boolean test() { - return true; - } +public boolean test() { + return true; +} ---- it's clear that `m()` will return an `X`, and, moreover, that can be @@ -902,11 +1065,11 @@ Suppose we add the resource method [source,java] ---- - @GET - @Path("greet/response") - public Response response(String name) { - return Response.ok("hello " + name).build(); - } +@GET +@Path("greet/response") +public Response response(String name) { + return Response.ok("hello " + name).build(); +} ---- to `org.greet.Greeter`. Then there is a new rpc entry @@ -921,11 +1084,11 @@ and the oneof field of `GeneralReturnMessage` becomes [source,protobuf] ---- - oneof messageType { - org_greet___Greeting org_greet___Greeting_field = 4; - org_greet___GeneralGreeting org_greet___GeneralGreeting_field = 5; - google.protobuf.Any google_protobuf_Any_field = 6; - } +oneof messageType { + org_greet___Greeting org_greet___Greeting_field = 4; + org_greet___GeneralGreeting org_greet___GeneralGreeting_field = 5; + google.protobuf.Any google_protobuf_Any_field = 6; +} ---- augmented by the `google_protobuf_Any_field` field. @@ -938,21 +1101,21 @@ Consider the following method: [source,java] ---- - @GET - @Path("suspend") - public void suspend(@Suspended final AsyncResponse response) { - Thread t = new Thread() { - @Override - public void run() { - try { - response.resume("suspend"); - } catch (Exception e) { - response.resume(e); - } - } - }; - t.start(); - } +@GET +@Path("suspend") +public void suspend(@Suspended final AsyncResponse response) { + Thread t = new Thread() { + @Override + public void run() { + try { + response.resume("suspend"); + } catch (Exception e) { + response.resume(e); + } + } + }; + t.start(); +} ---- This results in the rpc @@ -987,7 +1150,7 @@ methods in `GreetServiceGrpc` that need to be overridden. For example, [source,java] ---- - public void greet(org.greet.Greet_proto.GeneralEntityMessage param, StreamObserver responseObserver); +public void greet(org.greet.Greet_proto.GeneralEntityMessage param, StreamObserver responseObserver); ---- will be overridden by @@ -998,29 +1161,29 @@ will be overridden by public void greet(org.greet.Greet_proto.GeneralEntityMessage param, StreamObserver responseObserver) { HttpServletRequest request = null; try { - HttpServletResponseImpl response = new HttpServletResponseImpl("org_greet___Greeting", "sync", Greet_Server.getContext(), builder, fd); // 1 - GeneratedMessageV3 actualParam = param.getGStringField(); - request = getHttpServletRequest(param, actualParam, "//greet", response, "GET", "org_greet___Greeting"); // 2 - HttpServletDispatcher servlet = getServlet(); // 3 - activateRequestContext(); // 4 - servlet.service(request.getMethod(), request, response); // 5 - MockServletOutputStream msos = (MockServletOutputStream) response.getOutputStream(); - ByteArrayOutputStream baos = msos.getDelegate(); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - org_greet___Greeting reply = org_greet___Greeting.parseFrom(bais); // 6 - org.greet.Greet_proto.GeneralReturnMessage.Builder grmb = createGeneralReturnMessageBuilder(response); - grmb.setOrgGreetGreetingField(reply); - responseObserver.onNext(grmb.build()); // 7 + HttpServletResponseImpl response = new HttpServletResponseImpl("org_greet___Greeting", "sync", Greet_Server.getContext(), builder, fd); // 1 + GeneratedMessageV3 actualParam = param.getGStringField(); + request = getHttpServletRequest(param, actualParam, "//greet", response, "GET", "org_greet___Greeting"); // 2 + HttpServletDispatcher servlet = getServlet(); // 3 + activateRequestContext(); // 4 + servlet.service(request.getMethod(), request, response); // 5 + MockServletOutputStream msos = (MockServletOutputStream) response.getOutputStream(); + ByteArrayOutputStream baos = msos.getDelegate(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + org_greet___Greeting reply = org_greet___Greeting.parseFrom(bais); // 6 + org.greet.Greet_proto.GeneralReturnMessage.Builder grmb = createGeneralReturnMessageBuilder(response); + grmb.setOrgGreetGreetingField(reply); + responseObserver.onNext(grmb.build()); // 7 } catch (Exception e) { - responseObserver.onError(e); + responseObserver.onError(e); } finally { - responseObserver.onCompleted(); - if (requestContextController != null) { - requestContextController.deactivate(); - } - if (tccl != null) { - Thread.currentThread().setContextClassLoader(tccl); - } + responseObserver.onCompleted(); + if (requestContextController != null) { + requestContextController.deactivate(); + } + if (tccl != null) { + Thread.currentThread().setContextClassLoader(tccl); + } } } ---- @@ -1043,11 +1206,11 @@ Note that the sequence [source] ---- - org.greet.Greeting (Java class) - -> (translated by JavaToProtobufGenerator) -> - -> org_greet___Greeting (protobuf message) - -> (compiled by protoc) -> - -> org.greet.Greet_proto.org_greet___Greeting (Java class) + org.greet.Greeting (Java class) + -> (translated by JavaToProtobufGenerator) -> + -> org_greet___Greeting (protobuf message) + -> (compiled by protoc) -> + -> org.greet.Greet_proto.org_greet___Greeting (Java class) ---- turns the Java class `org.greet.Greeting` into a second Java class @@ -1064,9 +1227,9 @@ prefix will change), which has three methods [source,java] ---- - Message translateToJavabuf(Object o); - Message translateToJavabuf(Object o, GenericType genericType); - Object translateFromJavabuf(Message message); +Message translateToJavabuf(Object o); +Message translateToJavabuf(Object o, GenericType genericType); +Object translateFromJavabuf(Message message); ---- which do the translations. Without going too deeply into @@ -1075,8 +1238,8 @@ type; for example, [source,java] ---- - static class org_greet___Greeting_ToJavabuf implements TranslateToJavabuf { ... } - static class org_greet___Greeting_FromJavabuf implements TranslateFromJavabuf { ... } +static class org_greet___Greeting_ToJavabuf implements TranslateToJavabuf { ... } +static class org_greet___Greeting_FromJavabuf implements TranslateFromJavabuf { ... } ---- Each class has a list of lambdas, each lambda being responsible for @@ -1103,61 +1266,57 @@ example, that can be accomplished in a web.xml file as follows: [source,xml] ---- - - GreetServlet - - dev.resteasy.grpc.bridge.runtime.servlet.GrpcHttpServletDispatcher - - - - - - resteasy.use.builtin.providers - false - - - resteasy.servlet.mapping.prefix - /grpcToJakartaRest - - ... - - resteasy.providers - - org.jboss.resteasy.client.jaxrs.internal.CompletionStageRxInvokerProvider, - org.jboss.resteasy.plugins.interceptors.CacheControlFeature, - org.jboss.resteasy.plugins.interceptors.ClientContentEncodingAnnotationFeature, - org.jboss.resteasy.plugins.interceptors.MessageSanitizerContainerResponseFilter, - org.jboss.resteasy.plugins.interceptors.ServerContentEncodingAnnotationFeature, - org.jboss.resteasy.plugins.providers.AsyncStreamingOutputProvider, - org.jboss.resteasy.plugins.providers.CompletionStageProvider, - org.jboss.resteasy.plugins.providers.jackson.PatchMethodFilter, - org.jboss.resteasy.plugins.providers.jackson.UnrecognizedPropertyExceptionHandler, - org.jboss.resteasy.plugins.providers.jaxb.XmlJAXBContextFinder, - org.jboss.resteasy.plugins.providers.jsonp.JsonpPatchMethodFilter, - org.jboss.resteasy.plugins.providers.ReactiveStreamProvider, - org.jboss.resteasy.plugins.validation.ResteasyViolationExceptionMapper, - org.jboss.resteasy.plugins.validation.ValidatorContextResolver, - org.jboss.resteasy.plugins.validation.ValidatorContextResolverCDI, - org.jboss.resteasy.security.doseta.ClientDigitalSigningHeaderDecoratorFeature, - org.jboss.resteasy.security.doseta.ClientDigitalVerificationHeaderDecoratorFeature, - org.jboss.resteasy.security.doseta.DigitalSigningInterceptor, - org.jboss.resteasy.security.doseta.DigitalVerificationInterceptor, - org.jboss.resteasy.security.doseta.ServerDigitalSigningHeaderDecoratorFeature, - org.jboss.resteasy.security.doseta.ServerDigitalVerificationHeaderDecoratorFeature - - - - - GreetServlet - /grpcToJakartaRest/* - + + GreetServlet + + dev.resteasy.grpc.bridge.runtime.servlet.GrpcHttpServletDispatcher + + + + + resteasy.use.builtin.providers + false + + + resteasy.servlet.mapping.prefix + /grpcToJakartaRest + +... + + resteasy.providers + + org.jboss.resteasy.client.jaxrs.internal.CompletionStageRxInvokerProvider, + org.jboss.resteasy.plugins.interceptors.CacheControlFeature, + org.jboss.resteasy.plugins.interceptors.ClientContentEncodingAnnotationFeature, + org.jboss.resteasy.plugins.interceptors.MessageSanitizerContainerResponseFilter, + org.jboss.resteasy.plugins.interceptors.ServerContentEncodingAnnotationFeature, + org.jboss.resteasy.plugins.providers.AsyncStreamingOutputProvider, + org.jboss.resteasy.plugins.providers.CompletionStageProvider, + org.jboss.resteasy.plugins.providers.jackson.PatchMethodFilter, + org.jboss.resteasy.plugins.providers.jackson.UnrecognizedPropertyExceptionHandler, + org.jboss.resteasy.plugins.providers.jaxb.XmlJAXBContextFinder, + org.jboss.resteasy.plugins.providers.jsonp.JsonpPatchMethodFilter, + org.jboss.resteasy.plugins.providers.ReactiveStreamProvider, + org.jboss.resteasy.plugins.validation.ResteasyViolationExceptionMapper, + org.jboss.resteasy.plugins.validation.ValidatorContextResolver, + org.jboss.resteasy.plugins.validation.ValidatorContextResolverCDI, + org.jboss.resteasy.security.doseta.ClientDigitalSigningHeaderDecoratorFeature, + org.jboss.resteasy.security.doseta.ClientDigitalVerificationHeaderDecoratorFeature, + org.jboss.resteasy.security.doseta.DigitalSigningInterceptor, + org.jboss.resteasy.security.doseta.DigitalVerificationInterceptor, + org.jboss.resteasy.security.doseta.ServerDigitalSigningHeaderDecoratorFeature, + org.jboss.resteasy.security.doseta.ServerDigitalVerificationHeaderDecoratorFeature + + + + GreetServlet + /grpcToJakartaRest/* + ---- Of course, the list of providers can be reduced to those that are @@ -1179,14 +1338,12 @@ A. Create it directly using the appropriate `Builder`, or B. create a Java entity and translate it to javabuf with the `JavabufTranslator`. For example, to create an instance of `java_util___HashSet3`, defined in -<>, one option would be - -[source,java] +<>, one option would be ---- java.util.HashSet set = new java.util.HashSet(); set.add("abc"); GenericType> type - = new GenericType>() { }; += new GenericType>() { }; java_util___HashSet3 hashSet3 = (java_util___HashSet3) translator.translateToJavabuf(set, type); ---- and the other would be @@ -1198,16 +1355,16 @@ builder.addData("abc"); java_util___HashSet3 hashSet3 = builder.build(); ---- -*Note.* How did we know which javabuf type to use for a given invocation? +*Note.* How did we know which generic type and which javabuf type to use for a given invocation? It's easy. First, look at the resource method. Suppose we're going to call [source,java] ---- - @Path("hashset/string") - @POST - public HashSet hashSetTest3(HashSet l) { - return l; - } +@Path("hashset/string") +@POST +public HashSet hashSetTest3(HashSet l) { + return l; +} ---- It's expecting an instance of `HashSet`. Now, we have to figure out which javabuf type represents `HashSet`. Go to `Greet.proto` and search @@ -1260,6 +1417,9 @@ java_util___HashSet3 response = grm.getJavaUtilHashSet3Field(); HashSet result = (org.greet.Greeting) translator.translateFromJavabuf(response); ---- +*Note.* If the type on the resource method is open (has a wildcard or uninstantiated type variable), +the normalized version of the type is the one to use. + A variation of the client code occurs when the entity and/or result type is an interface, since they need to be transmitted as `Any` messages. For example, [source,java] @@ -1293,24 +1453,23 @@ A few changes are necessary to support SSE streaming. Suppose [source,java] ---- - private ArrayList names = new ArrayList(); - - @GET - @Path("stream") - @Produces(MediaType.SERVER_SENT_EVENTS) - public void sseGreet(@Context SseEventSink eventSink, @Context Sse sse) { - ExecutorService executor = Executors.newFixedThreadPool(3); - final Map, Object> map = ResteasyContext.getContextDataMap(); - executor.execute(() -> { - ResteasyContext.addCloseableContextDataLevel(map); - try (SseEventSink sink = eventSink) { - Iterator it = names.iterator(); - while (it.hasNext()) { - eventSink.send(sse.newEvent("hello, " + it.next())); - } - } - }); - } +private ArrayList names = new ArrayList(); + @GET +@Path("stream") +@Produces(MediaType.SERVER_SENT_EVENTS) +public void sseGreet(@Context SseEventSink eventSink, @Context Sse sse) { + ExecutorService executor = Executors.newFixedThreadPool(3); + final Map, Object> map = ResteasyContext.getContextDataMap(); + executor.execute(() -> { + ResteasyContext.addCloseableContextDataLevel(map); + try (SseEventSink sink = eventSink) { + Iterator it = names.iterator(); + while (it.hasNext()) { + eventSink.send(sse.newEvent("hello, " + it.next())); + } + } + }); +} ---- A couple of additions appear in Greet.proto: @@ -1344,48 +1503,48 @@ multiple return messages: [source,java] ---- - @java.lang.Override - public void sseGreet(org.greet.Greet_proto.GeneralEntityMessage param, StreamObserver responseObserver) { - HttpServletRequest request = null; - try { - HttpServletResponseImpl response = new HttpServletResponseImpl("org_jboss_resteasy_grpc_sse_runtime___SseEvent", "sse", Greet_Server.getContext(), builder, fd); - GeneratedMessageV3 actualParam = param.getGEmptyField(); - request = getHttpServletRequest(param, actualParam, "/stream", response, "GET", "org_jboss_resteasy_grpc_sse_runtime___SseEvent"); - HttpServletDispatcher servlet = getServlet(); - activateRequestContext(); - servlet.service(request.getMethod(), request, response); - AsyncMockServletOutputStream amsos = (AsyncMockServletOutputStream) response.getOutputStream(); - while (true) { - if (amsos.isClosed()) { - break; - } - ByteArrayOutputStream baos = amsos.await(); - if (amsos.isClosed()) { - break; - } - byte[] bytes = baos.toByteArray(); - if (bytes.length == 2 && bytes[0] == 10 && bytes[1] == 10) { - continue; - } - try { - org_jboss_resteasy_grpc_runtime_sse___SseEvent sseEvent = org_jboss_resteasy_grpc_runtime_sse___SseEvent.parseFrom(bytes); - responseObserver.onNext(sseEvent); - } catch (Exception e) { - continue; - } +@java.lang.Override +public void sseGreet(org.greet.Greet_proto.GeneralEntityMessage param, StreamObserver responseObserver) { + HttpServletRequest request = null; + try { + HttpServletResponseImpl response = new HttpServletResponseImpl("org_jboss_resteasy_grpc_sse_runtime___SseEvent", "sse", Greet_Server.getContext(), builder, fd); + GeneratedMessageV3 actualParam = param.getGEmptyField(); + request = getHttpServletRequest(param, actualParam, "/stream", response, "GET", "org_jboss_resteasy_grpc_sse_runtime___SseEvent"); + HttpServletDispatcher servlet = getServlet(); + activateRequestContext(); + servlet.service(request.getMethod(), request, response); + AsyncMockServletOutputStream amsos = (AsyncMockServletOutputStream) response.getOutputStream(); + while (true) { + if (amsos.isClosed()) { + break; + } + ByteArrayOutputStream baos = amsos.await(); + if (amsos.isClosed()) { + break; } - } catch (Exception e) { - responseObserver.onError(e); - } finally { - responseObserver.onCompleted(); - if (requestContextController != null) { - requestContextController.deactivate(); + byte[] bytes = baos.toByteArray(); + if (bytes.length == 2 && bytes[0] == 10 && bytes[1] == 10) { + continue; } - if (tccl != null) { - Thread.currentThread().setContextClassLoader(tccl); + try { + org_jboss_resteasy_grpc_runtime_sse___SseEvent sseEvent = org_jboss_resteasy_grpc_runtime_sse___SseEvent.parseFrom(bytes); + responseObserver.onNext(sseEvent); + } catch (Exception e) { + continue; } } + } catch (Exception e) { + responseObserver.onError(e); + } finally { + responseObserver.onCompleted(); + if (requestContextController != null) { + requestContextController.deactivate(); + } + if (tccl != null) { + Thread.currentThread().setContextClassLoader(tccl); + } } +} ---- These changes are generated automatically, so no intervention is @@ -1394,13 +1553,13 @@ adjusted. It could look, for example, something like this: [source,java] ---- - Iterator response = blockingStub.sseGreet(gem); - while (response.hasNext()) { - org_jboss_resteasy_grpc_runtime_sse___SseEvent sseEvent = response.next(); - Any any = sseEvent.getData(); - gString gString = any.unpack(gString.class); - System.out.println(gString.getValue()); - } +Iterator response = blockingStub.sseGreet(gem); +while (response.hasNext()) { + org_jboss_resteasy_grpc_runtime_sse___SseEvent sseEvent = response.next(); + Any any = sseEvent.getData(); + gString gString = any.unpack(gString.class); + System.out.println(gString.getValue()); +} ---- Note, in particular, the treatment of the `data` field. The class @@ -1412,7 +1571,7 @@ Note, in particular, the treatment of the `data` field. The class [source,java] ---- - public static Any pack(T message, java.lang.String typeUrlPrefix); +public static Any pack(T message, java.lang.String typeUrlPrefix); ---- so we have to translate the `Object` into a `Message`; The translation @@ -1448,17 +1607,17 @@ generate the initial state of the bridge project, run [source,bash] ---- - mvn archetype:generate -B \ - -DarchetypeGroupId=dev.resteasy.grpc \ - -DarchetypeArtifactId=gRPCtoJakartaREST-archetype \ - -DarchetypeVersion=${archetype.version} \ - -DgroupId=org.greet \ - -DartifactId=greet \ - -Dversion=0.0.1 \ - -Dgenerate-prefix=Greet \ - -Dgenerate-package=org.greet \ - -Dresteasy-version=${resteasy.version} \ - -Dgrpc-bridge-version=${resteasy.grpc.version} +mvn archetype:generate -B \ + -DarchetypeGroupId=dev.resteasy.grpc \ + -DarchetypeArtifactId=gRPCtoJakartaREST-archetype \ + -DarchetypeVersion=${archetype.version} \ + -DgroupId=org.greet \ + -DartifactId=greet \ + -Dversion=0.0.1 \ + -Dgenerate-prefix=Greet \ + -Dgenerate-package=org.greet \ + -Dresteasy-version=${resteasy.version} \ + -Dgrpc-bridge-version=${resteasy.grpc.version} ---- The following parameters need to be supplied: @@ -1482,15 +1641,15 @@ new project is [source] ---- - +- pom.xml - +- src/main/webapp - | +- META-INF - | | +- beans.xml - | +- WEB-INF - | +- web.xml - +- src/main/resources - | +- buildjar - | +- deployjar ++- pom.xml ++- src/main/webapp +| +- META-INF +| | +- beans.xml +| +- WEB-INF +| +- web.xml ++- src/main/resources +| +- buildjar +| +- deployjar ---- The most important file is pom.xml, which describes the sequence of @@ -1510,7 +1669,7 @@ classes: [source,bash] ---- - mvn clean install +mvn clean install ---- There are also some optional parameters: @@ -1525,7 +1684,7 @@ The syntax for the "classes" parameter is [source] ---- - (DIR ":" CLASSNAME) ("," DIR ":" CLASSNAME)* + (DIR ":" CLASSNAME) ("," DIR ":" CLASSNAME)* ---- where @@ -1537,43 +1696,43 @@ For example, [source,bash] ---- - mvn -Dclasses=/home/bob/greet/src/java/main:org.greet.Extra clean install +mvn -Dclasses=/home/bob/greet/src/java/main:org.greet.Extra clean install ---- When the project is built, the layout is as follows: [source] ---- - +- pom.xml - +- src/main/java - | +- org.greet - | +- GeneralGreeting.java - | + Greeter.java - | + Greeting.java - +- src/main/proto - | +- Greet.proto - +- src/main/webapp - | +- META-INF - | | +- beans.xml - | +- WEB-INF - | +- web.xml - +- src/main/resources - | +- buildjar - | +- deployjar - +- target/generated-sources/protobuf - | +- java - | +- org.greet - | | +- Greet_proto.java - | +- grpc-java - | +- org.greet - | +- Greet_Server.java - | +- GreetJavabufTranslator.java - | +- GreetMessageBodyReaderWriter.java - | +- GreetServiceGrpc.java - | +- GreetServiceGrpcImpl.java - | +- greet.grpc-0.0.1.jar - | +- greet.grpc-0.0.1.war - | +- greet.grpc-0.0.1-sources.jar ++- pom.xml ++- src/main/java +| +- org.greet +| +- GeneralGreeting.java +| + Greeter.java +| + Greeting.java ++- src/main/proto +| +- Greet.proto ++- src/main/webapp +| +- META-INF +| | +- beans.xml +| +- WEB-INF +| +- web.xml ++- src/main/resources +| +- buildjar +| +- deployjar ++- target/generated-sources/protobuf +| +- java +| +- org.greet +| | +- Greet_proto.java +| +- grpc-java +| +- org.greet +| +- Greet_Server.java +| +- GreetJavabufTranslator.java +| +- GreetMessageBodyReaderWriter.java +| +- GreetServiceGrpc.java +| +- GreetServiceGrpcImpl.java +| +- greet.grpc-0.0.1.jar +| +- greet.grpc-0.0.1.war +| +- greet.grpc-0.0.1-sources.jar ---- *Notes* @@ -1656,14 +1815,14 @@ in the resteasy-grpc-testsuite in resteasy-grpc: [source,java] ---- - try ( - Client client = ClientBuilder.newClient(); - var response = client.target("http://localhost:8080/grpc-test/grpcserver/context") - .request() - .get()) { - final var message = response.getStatus() + ": " + response.readEntity(String.class); - Assert.assertEquals(message, 204, response.getStatus()); - } +try ( + Client client = ClientBuilder.newClient(); + var response = client.target("http://localhost:8080/grpc-test/grpcserver/context") + .request() + .get()) { + final var message = response.getStatus() + ": " + response.readEntity(String.class); + Assert.assertEquals(message, 204, response.getStatus()); +} ---- By the way, `AbstractGrpcToJakartaRESTTest` has a lot of client side code that might be useful to look at. @@ -1744,16 +1903,16 @@ out any information needed for a given computation. Recall that [source,protobuf] ---- - message GeneralEntityMessage { - ServletInfo servletInfo = 1; - string URL = 2; - map headers = 3; - repeated gCookie cookies = 4; - string httpMethod = 5; - oneof messageType { - ... - } - } +message GeneralEntityMessage { + ServletInfo servletInfo = 1; + string URL = 2; + map headers = 3; + repeated gCookie cookies = 4; + string httpMethod = 5; + oneof messageType { + ... + } +} ---- Some of these fields, e.g., cookies and headers, are naturally supplied @@ -1761,12 +1920,12 @@ by the client. On the other hand, the information in [source,protobuf] ---- - message ServletInfo { - string characterEncoding = 1; - string clientAddress = 2; - string clientHost = 3; - int32 clientPort = 4; - } +message ServletInfo { + string characterEncoding = 1; + string clientAddress = 2; + string clientHost = 3; + int32 clientPort = 4; +} ---- which would normally come from the network connection, must be supplied