diff options
-rw-r--r-- | .gitattributes | 1 | ||||
-rw-r--r-- | compiler/build.gradle | 20 | ||||
-rw-r--r-- | compiler/src/java_plugin/cpp/java_generator.cpp | 31 | ||||
-rw-r--r-- | compiler/src/java_plugin/cpp/java_generator.h | 6 | ||||
-rw-r--r-- | compiler/src/java_plugin/cpp/java_plugin.cpp | 15 | ||||
-rw-r--r-- | compiler/src/test/golden/TestServiceLite.java.txt | 323 | ||||
-rw-r--r-- | compiler/src/test/proto/test_lite.proto | 54 | ||||
-rw-r--r-- | protobuf-lite/build.gradle | 15 | ||||
-rw-r--r-- | protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoInputStream.java (renamed from protobuf/src/main/java/io/grpc/protobuf/ProtoInputStream.java) | 2 | ||||
-rw-r--r-- | protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java | 136 | ||||
-rw-r--r-- | protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java | 214 | ||||
-rw-r--r-- | protobuf/build.gradle | 1 | ||||
-rw-r--r-- | protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java | 92 | ||||
-rw-r--r-- | protobuf/src/test/java/io/grpc/protobuf/ProtoUtilsTest.java | 161 | ||||
-rw-r--r-- | settings.gradle | 2 |
15 files changed, 817 insertions, 256 deletions
diff --git a/.gitattributes b/.gitattributes index af34525bb..6754557ea 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ TestService.java.txt binary +TestServiceLite.java.txt binary TestServiceNano.java.txt binary diff --git a/compiler/build.gradle b/compiler/build.gradle index a48799297..740bf4b1e 100644 --- a/compiler/build.gradle +++ b/compiler/build.gradle @@ -127,6 +127,11 @@ sourceSets { } } +compileTestJava { + // Protobuf-generated Lite produces quite a few warnings. + it.options.compilerArgs.removeAll(["-Xlint:unchecked", "-Xlint:rawtypes"]) +} + protobuf { protoc { if (project.hasProperty('protoc')) { @@ -223,10 +228,11 @@ artifacts { } } -test.dependsOn('testGolden', 'testNanoGolden') +test.dependsOn('testGolden', 'testLiteGolden', 'testNanoGolden') -def configureTestTask(Task task, String suffix, String extraPackage) { - task.dependsOn "generateTest${suffix}Proto" +def configureTestTask(Task task, String suffix, String dep, + String extraPackage) { + task.dependsOn "generateTest${dep}Proto" if (osdetector.os != 'windows') { task.executable "diff" } else { @@ -234,11 +240,13 @@ def configureTestTask(Task task, String suffix, String extraPackage) { } // File isn't found on Windows if last slash is forward-slash def slash = System.getProperty("file.separator") - task.args "$buildDir/generated/source/proto/test${suffix}/grpc/io/grpc/testing/integration${extraPackage}${slash}TestServiceGrpc.java", + task.args "$buildDir/generated/source/proto/test${dep}/grpc/io/grpc/testing/integration${extraPackage}${slash}TestServiceGrpc.java", "$projectDir/src/test/golden/TestService${suffix}.java.txt" } task testGolden(type: Exec) +task testLiteGolden(type: Exec) task testNanoGolden(type: Exec) -configureTestTask(testGolden, '', '') -configureTestTask(testNanoGolden, 'Nano', '/nano') +configureTestTask(testGolden, '', '', '') +configureTestTask(testLiteGolden, 'Lite', '', '/lite') +configureTestTask(testNanoGolden, 'Nano', 'Nano', '/nano') diff --git a/compiler/src/java_plugin/cpp/java_generator.cpp b/compiler/src/java_plugin/cpp/java_generator.cpp index 30a62229a..cf8cdbe79 100644 --- a/compiler/src/java_plugin/cpp/java_generator.cpp +++ b/compiler/src/java_plugin/cpp/java_generator.cpp @@ -83,7 +83,7 @@ static inline string MessageFullJavaName(bool nano, const Descriptor* desc) { static void PrintMethodFields( const ServiceDescriptor* service, map<string, string>* vars, Printer* p, - bool generate_nano) { + ProtoFlavor flavor) { p->Print("// Static method descriptors that strictly reflect the proto.\n"); (*vars)["service_name"] = service->name(); for (int i = 0; i < service->method_count(); ++i) { @@ -91,9 +91,9 @@ static void PrintMethodFields( (*vars)["arg_in_id"] = to_string(2 * i); (*vars)["arg_out_id"] = to_string(2 * i + 1); (*vars)["method_name"] = method->name(); - (*vars)["input_type"] = MessageFullJavaName(generate_nano, + (*vars)["input_type"] = MessageFullJavaName(flavor == ProtoFlavor::NANO, method->input_type()); - (*vars)["output_type"] = MessageFullJavaName(generate_nano, + (*vars)["output_type"] = MessageFullJavaName(flavor == ProtoFlavor::NANO, method->output_type()); (*vars)["method_field_name"] = MethodPropertiesFieldName(method); bool client_streaming = method->client_streaming(); @@ -112,7 +112,7 @@ static void PrintMethodFields( } } - if (generate_nano) { + if (flavor == ProtoFlavor::NANO) { // TODO(zsurocking): we're creating two NanoFactories for each method right now. // We could instead create static NanoFactories and reuse them if some methods // share the same request or response messages. @@ -133,6 +133,11 @@ static void PrintMethodFields( " new NanoFactory<$output_type$>(ARG_OUT_$method_field_name$))\n" " );\n"); } else { + if (flavor == ProtoFlavor::LITE) { + (*vars)["ProtoUtils"] = "io.grpc.protobuf.lite.ProtoLiteUtils"; + } else { + (*vars)["ProtoUtils"] = "io.grpc.protobuf.ProtoUtils"; + } p->Print( *vars, "@$ExperimentalApi$\n" @@ -148,7 +153,7 @@ static void PrintMethodFields( } p->Print("\n"); - if (generate_nano) { + if (flavor == ProtoFlavor::NANO) { p->Print( "private static final class NanoFactory<T extends com.google.protobuf.nano.MessageNano>\n" " implements io.grpc.protobuf.nano.MessageNanoFactory<T> {\n" @@ -162,6 +167,7 @@ static void PrintMethodFields( " public T newInstance() {\n" " Object o;\n" " switch (id) {\n"); + bool generate_nano = true; for (int i = 0; i < service->method_count(); ++i) { const MethodDescriptor* method = service->method(i); (*vars)["input_type"] = MessageFullJavaName(generate_nano, @@ -639,7 +645,7 @@ static void PrintBindServiceMethod(const ServiceDescriptor* service, static void PrintService(const ServiceDescriptor* service, map<string, string>* vars, Printer* p, - bool generate_nano) { + ProtoFlavor flavor) { (*vars)["service_name"] = service->name(); (*vars)["file_name"] = service->file()->name(); (*vars)["service_class_name"] = ServiceClassName(service); @@ -659,7 +665,7 @@ static void PrintService(const ServiceDescriptor* service, "public static final String SERVICE_NAME = " "\"$Package$$service_name$\";\n\n"); - PrintMethodFields(service, vars, p, generate_nano); + PrintMethodFields(service, vars, p, flavor); p->Print( *vars, @@ -691,6 +697,7 @@ static void PrintService(const ServiceDescriptor* service, p->Outdent(); p->Print("}\n\n"); + bool generate_nano = flavor == ProtoFlavor::NANO; PrintStub(service, vars, p, ASYNC_INTERFACE, generate_nano); PrintStub(service, vars, p, BLOCKING_CLIENT_INTERFACE, generate_nano); PrintStub(service, vars, p, FUTURE_CLIENT_INTERFACE, generate_nano); @@ -736,7 +743,7 @@ void PrintImports(Printer* p, bool generate_nano) { void GenerateService(const ServiceDescriptor* service, google::protobuf::io::ZeroCopyOutputStream* out, - bool generate_nano) { + ProtoFlavor flavor) { // All non-generated classes must be referred by fully qualified names to // avoid collision with generated classes. map<string, string> vars; @@ -753,7 +760,6 @@ void GenerateService(const ServiceDescriptor* service, vars["ImmutableList"] = "com.google.common.collect.ImmutableList"; vars["Collection"] = "java.util.Collection"; vars["MethodDescriptor"] = "io.grpc.MethodDescriptor"; - vars["ProtoUtils"] = "io.grpc.protobuf.ProtoUtils"; vars["NanoUtils"] = "io.grpc.protobuf.nano.NanoUtils"; vars["StreamObserver"] = "io.grpc.stub.StreamObserver"; vars["Iterator"] = "java.util.Iterator"; @@ -766,20 +772,21 @@ void GenerateService(const ServiceDescriptor* service, vars["ExperimentalApi"] = "io.grpc.ExperimentalApi"; Printer printer(out, '$'); - string package_name = ServiceJavaPackage(service->file(), generate_nano); + string package_name = ServiceJavaPackage(service->file(), + flavor == ProtoFlavor::NANO); if (!package_name.empty()) { printer.Print( "package $package_name$;\n\n", "package_name", package_name); } - PrintImports(&printer, generate_nano); + PrintImports(&printer, flavor == ProtoFlavor::NANO); // Package string is used to fully qualify method names. vars["Package"] = service->file()->package(); if (!vars["Package"].empty()) { vars["Package"].append("."); } - PrintService(service, &vars, &printer, generate_nano); + PrintService(service, &vars, &printer, flavor); } string ServiceJavaPackage(const FileDescriptor* file, bool nano) { diff --git a/compiler/src/java_plugin/cpp/java_generator.h b/compiler/src/java_plugin/cpp/java_generator.h index 96b3b8aaa..29c6fd952 100644 --- a/compiler/src/java_plugin/cpp/java_generator.h +++ b/compiler/src/java_plugin/cpp/java_generator.h @@ -38,6 +38,10 @@ using namespace std; namespace java_grpc_generator { +enum ProtoFlavor { + NORMAL, LITE, NANO +}; + // Returns the package name of the gRPC services defined in the given file. string ServiceJavaPackage(const google::protobuf::FileDescriptor* file, bool nano); @@ -48,7 +52,7 @@ string ServiceClassName(const google::protobuf::ServiceDescriptor* service); // Writes the generated service interface into the given ZeroCopyOutputStream void GenerateService(const google::protobuf::ServiceDescriptor* service, google::protobuf::io::ZeroCopyOutputStream* out, - bool generate_nano); + ProtoFlavor flavor); } // namespace java_grpc_generator diff --git a/compiler/src/java_plugin/cpp/java_plugin.cpp b/compiler/src/java_plugin/cpp/java_plugin.cpp index 8f9a2e81f..1c95497ba 100644 --- a/compiler/src/java_plugin/cpp/java_plugin.cpp +++ b/compiler/src/java_plugin/cpp/java_plugin.cpp @@ -9,6 +9,7 @@ #include <google/protobuf/compiler/code_generator.h> #include <google/protobuf/compiler/plugin.h> #include <google/protobuf/descriptor.h> +#include <google/protobuf/descriptor.pb.h> #include <google/protobuf/io/zero_copy_stream.h> static string JavaPackageToDir(const string& package_name) { @@ -34,14 +35,20 @@ class JavaGrpcGenerator : public google::protobuf::compiler::CodeGenerator { vector<pair<string, string> > options; google::protobuf::compiler::ParseGeneratorParameter(parameter, &options); - bool generate_nano = false; + java_grpc_generator::ProtoFlavor flavor = + java_grpc_generator::ProtoFlavor::NORMAL; + if (file->options().optimize_for() == + google::protobuf::FileOptions::LITE_RUNTIME) { + flavor = java_grpc_generator::ProtoFlavor::LITE; + } for (int i = 0; i < options.size(); i++) { if (options[i].first == "nano" && options[i].second == "true") { - generate_nano = true; + flavor = java_grpc_generator::ProtoFlavor::NANO; } } - string package_name = java_grpc_generator::ServiceJavaPackage(file, generate_nano); + string package_name = java_grpc_generator::ServiceJavaPackage( + file, flavor == java_grpc_generator::ProtoFlavor::NANO); string package_filename = JavaPackageToDir(package_name); for (int i = 0; i < file->service_count(); ++i) { const google::protobuf::ServiceDescriptor* service = file->service(i); @@ -49,7 +56,7 @@ class JavaGrpcGenerator : public google::protobuf::compiler::CodeGenerator { + java_grpc_generator::ServiceClassName(service) + ".java"; std::unique_ptr<google::protobuf::io::ZeroCopyOutputStream> output( context->Open(filename)); - java_grpc_generator::GenerateService(service, output.get(), generate_nano); + java_grpc_generator::GenerateService(service, output.get(), flavor); } return true; } diff --git a/compiler/src/test/golden/TestServiceLite.java.txt b/compiler/src/test/golden/TestServiceLite.java.txt new file mode 100644 index 000000000..86da7e77e --- /dev/null +++ b/compiler/src/test/golden/TestServiceLite.java.txt @@ -0,0 +1,323 @@ +package io.grpc.testing.integration.lite; + +import static io.grpc.stub.ClientCalls.asyncUnaryCall; +import static io.grpc.stub.ClientCalls.asyncServerStreamingCall; +import static io.grpc.stub.ClientCalls.asyncClientStreamingCall; +import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall; +import static io.grpc.stub.ClientCalls.blockingUnaryCall; +import static io.grpc.stub.ClientCalls.blockingServerStreamingCall; +import static io.grpc.stub.ClientCalls.futureUnaryCall; +import static io.grpc.MethodDescriptor.generateFullMethodName; +import static io.grpc.stub.ServerCalls.asyncUnaryCall; +import static io.grpc.stub.ServerCalls.asyncServerStreamingCall; +import static io.grpc.stub.ServerCalls.asyncClientStreamingCall; +import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall; + +@javax.annotation.Generated( + value = "by gRPC proto compiler", + comments = "Source: test_lite.proto") +public class TestServiceGrpc { + + private TestServiceGrpc() {} + + public static final String SERVICE_NAME = "grpc.testing.lite.TestService"; + + // Static method descriptors that strictly reflect the proto. + @io.grpc.ExperimentalApi + public static final io.grpc.MethodDescriptor<io.grpc.testing.integration.lite.TestLite.SimpleRequest, + io.grpc.testing.integration.lite.TestLite.SimpleResponse> METHOD_UNARY_CALL = + io.grpc.MethodDescriptor.create( + io.grpc.MethodDescriptor.MethodType.UNARY, + generateFullMethodName( + "grpc.testing.lite.TestService", "UnaryCall"), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.SimpleRequest.getDefaultInstance()), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.SimpleResponse.getDefaultInstance())); + @io.grpc.ExperimentalApi + public static final io.grpc.MethodDescriptor<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest, + io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> METHOD_STREAMING_OUTPUT_CALL = + io.grpc.MethodDescriptor.create( + io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING, + generateFullMethodName( + "grpc.testing.lite.TestService", "StreamingOutputCall"), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest.getDefaultInstance()), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse.getDefaultInstance())); + @io.grpc.ExperimentalApi + public static final io.grpc.MethodDescriptor<io.grpc.testing.integration.lite.TestLite.StreamingInputCallRequest, + io.grpc.testing.integration.lite.TestLite.StreamingInputCallResponse> METHOD_STREAMING_INPUT_CALL = + io.grpc.MethodDescriptor.create( + io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING, + generateFullMethodName( + "grpc.testing.lite.TestService", "StreamingInputCall"), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingInputCallRequest.getDefaultInstance()), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingInputCallResponse.getDefaultInstance())); + @io.grpc.ExperimentalApi + public static final io.grpc.MethodDescriptor<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest, + io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> METHOD_FULL_BIDI_CALL = + io.grpc.MethodDescriptor.create( + io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING, + generateFullMethodName( + "grpc.testing.lite.TestService", "FullBidiCall"), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest.getDefaultInstance()), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse.getDefaultInstance())); + @io.grpc.ExperimentalApi + public static final io.grpc.MethodDescriptor<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest, + io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> METHOD_HALF_BIDI_CALL = + io.grpc.MethodDescriptor.create( + io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING, + generateFullMethodName( + "grpc.testing.lite.TestService", "HalfBidiCall"), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest.getDefaultInstance()), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse.getDefaultInstance())); + + public static TestServiceStub newStub(io.grpc.Channel channel) { + return new TestServiceStub(channel); + } + + public static TestServiceBlockingStub newBlockingStub( + io.grpc.Channel channel) { + return new TestServiceBlockingStub(channel); + } + + public static TestServiceFutureStub newFutureStub( + io.grpc.Channel channel) { + return new TestServiceFutureStub(channel); + } + + public static interface TestService { + + public void unaryCall(io.grpc.testing.integration.lite.TestLite.SimpleRequest request, + io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.SimpleResponse> responseObserver); + + public void streamingOutputCall(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest request, + io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> responseObserver); + + public io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingInputCallRequest> streamingInputCall( + io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingInputCallResponse> responseObserver); + + public io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest> fullBidiCall( + io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> responseObserver); + + public io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest> halfBidiCall( + io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> responseObserver); + } + + public static interface TestServiceBlockingClient { + + public io.grpc.testing.integration.lite.TestLite.SimpleResponse unaryCall(io.grpc.testing.integration.lite.TestLite.SimpleRequest request); + + public java.util.Iterator<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> streamingOutputCall( + io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest request); + } + + public static interface TestServiceFutureClient { + + public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.integration.lite.TestLite.SimpleResponse> unaryCall( + io.grpc.testing.integration.lite.TestLite.SimpleRequest request); + } + + public static class TestServiceStub extends io.grpc.stub.AbstractStub<TestServiceStub> + implements TestService { + private TestServiceStub(io.grpc.Channel channel) { + super(channel); + } + + private TestServiceStub(io.grpc.Channel channel, + io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected TestServiceStub build(io.grpc.Channel channel, + io.grpc.CallOptions callOptions) { + return new TestServiceStub(channel, callOptions); + } + + @java.lang.Override + public void unaryCall(io.grpc.testing.integration.lite.TestLite.SimpleRequest request, + io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.SimpleResponse> responseObserver) { + asyncUnaryCall( + getChannel().newCall(METHOD_UNARY_CALL, getCallOptions()), request, responseObserver); + } + + @java.lang.Override + public void streamingOutputCall(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest request, + io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> responseObserver) { + asyncServerStreamingCall( + getChannel().newCall(METHOD_STREAMING_OUTPUT_CALL, getCallOptions()), request, responseObserver); + } + + @java.lang.Override + public io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingInputCallRequest> streamingInputCall( + io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingInputCallResponse> responseObserver) { + return asyncClientStreamingCall( + getChannel().newCall(METHOD_STREAMING_INPUT_CALL, getCallOptions()), responseObserver); + } + + @java.lang.Override + public io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest> fullBidiCall( + io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> responseObserver) { + return asyncBidiStreamingCall( + getChannel().newCall(METHOD_FULL_BIDI_CALL, getCallOptions()), responseObserver); + } + + @java.lang.Override + public io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest> halfBidiCall( + io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> responseObserver) { + return asyncBidiStreamingCall( + getChannel().newCall(METHOD_HALF_BIDI_CALL, getCallOptions()), responseObserver); + } + } + + public static class TestServiceBlockingStub extends io.grpc.stub.AbstractStub<TestServiceBlockingStub> + implements TestServiceBlockingClient { + private TestServiceBlockingStub(io.grpc.Channel channel) { + super(channel); + } + + private TestServiceBlockingStub(io.grpc.Channel channel, + io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected TestServiceBlockingStub build(io.grpc.Channel channel, + io.grpc.CallOptions callOptions) { + return new TestServiceBlockingStub(channel, callOptions); + } + + @java.lang.Override + public io.grpc.testing.integration.lite.TestLite.SimpleResponse unaryCall(io.grpc.testing.integration.lite.TestLite.SimpleRequest request) { + return blockingUnaryCall( + getChannel(), METHOD_UNARY_CALL, getCallOptions(), request); + } + + @java.lang.Override + public java.util.Iterator<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> streamingOutputCall( + io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest request) { + return blockingServerStreamingCall( + getChannel(), METHOD_STREAMING_OUTPUT_CALL, getCallOptions(), request); + } + } + + public static class TestServiceFutureStub extends io.grpc.stub.AbstractStub<TestServiceFutureStub> + implements TestServiceFutureClient { + private TestServiceFutureStub(io.grpc.Channel channel) { + super(channel); + } + + private TestServiceFutureStub(io.grpc.Channel channel, + io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected TestServiceFutureStub build(io.grpc.Channel channel, + io.grpc.CallOptions callOptions) { + return new TestServiceFutureStub(channel, callOptions); + } + + @java.lang.Override + public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.integration.lite.TestLite.SimpleResponse> unaryCall( + io.grpc.testing.integration.lite.TestLite.SimpleRequest request) { + return futureUnaryCall( + getChannel().newCall(METHOD_UNARY_CALL, getCallOptions()), request); + } + } + + private static final int METHODID_UNARY_CALL = 0; + private static final int METHODID_STREAMING_OUTPUT_CALL = 1; + private static final int METHODID_STREAMING_INPUT_CALL = 2; + private static final int METHODID_FULL_BIDI_CALL = 3; + private static final int METHODID_HALF_BIDI_CALL = 4; + + private static class MethodHandlers<Req, Resp> implements + io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>, + io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>, + io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>, + io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> { + private final TestService serviceImpl; + private final int methodId; + + public MethodHandlers(TestService serviceImpl, int methodId) { + this.serviceImpl = serviceImpl; + this.methodId = methodId; + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) { + switch (methodId) { + case METHODID_UNARY_CALL: + serviceImpl.unaryCall((io.grpc.testing.integration.lite.TestLite.SimpleRequest) request, + (io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.SimpleResponse>) responseObserver); + break; + case METHODID_STREAMING_OUTPUT_CALL: + serviceImpl.streamingOutputCall((io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest) request, + (io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse>) responseObserver); + break; + default: + throw new AssertionError(); + } + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public io.grpc.stub.StreamObserver<Req> invoke( + io.grpc.stub.StreamObserver<Resp> responseObserver) { + switch (methodId) { + case METHODID_STREAMING_INPUT_CALL: + return (io.grpc.stub.StreamObserver<Req>) serviceImpl.streamingInputCall( + (io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingInputCallResponse>) responseObserver); + case METHODID_FULL_BIDI_CALL: + return (io.grpc.stub.StreamObserver<Req>) serviceImpl.fullBidiCall( + (io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse>) responseObserver); + case METHODID_HALF_BIDI_CALL: + return (io.grpc.stub.StreamObserver<Req>) serviceImpl.halfBidiCall( + (io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse>) responseObserver); + default: + throw new AssertionError(); + } + } + } + + public static io.grpc.ServerServiceDefinition bindService( + final TestService serviceImpl) { + return io.grpc.ServerServiceDefinition.builder(SERVICE_NAME) + .addMethod( + METHOD_UNARY_CALL, + asyncUnaryCall( + new MethodHandlers< + io.grpc.testing.integration.lite.TestLite.SimpleRequest, + io.grpc.testing.integration.lite.TestLite.SimpleResponse>( + serviceImpl, METHODID_UNARY_CALL))) + .addMethod( + METHOD_STREAMING_OUTPUT_CALL, + asyncServerStreamingCall( + new MethodHandlers< + io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest, + io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse>( + serviceImpl, METHODID_STREAMING_OUTPUT_CALL))) + .addMethod( + METHOD_STREAMING_INPUT_CALL, + asyncClientStreamingCall( + new MethodHandlers< + io.grpc.testing.integration.lite.TestLite.StreamingInputCallRequest, + io.grpc.testing.integration.lite.TestLite.StreamingInputCallResponse>( + serviceImpl, METHODID_STREAMING_INPUT_CALL))) + .addMethod( + METHOD_FULL_BIDI_CALL, + asyncBidiStreamingCall( + new MethodHandlers< + io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest, + io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse>( + serviceImpl, METHODID_FULL_BIDI_CALL))) + .addMethod( + METHOD_HALF_BIDI_CALL, + asyncBidiStreamingCall( + new MethodHandlers< + io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest, + io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse>( + serviceImpl, METHODID_HALF_BIDI_CALL))) + .build(); + } +} diff --git a/compiler/src/test/proto/test_lite.proto b/compiler/src/test/proto/test_lite.proto new file mode 100644 index 000000000..0e422033e --- /dev/null +++ b/compiler/src/test/proto/test_lite.proto @@ -0,0 +1,54 @@ +// A simple service definition for testing the protoc plugin. +syntax = "proto2"; + +package grpc.testing.lite; + +option java_package = "io.grpc.testing.integration.lite"; +option optimize_for = LITE_RUNTIME; + +message SimpleRequest { +} + +message SimpleResponse { +} + +message StreamingInputCallRequest { +} + +message StreamingInputCallResponse { +} + +message StreamingOutputCallRequest { +} + +message StreamingOutputCallResponse { +} + +service TestService { + // One request followed by one response. + // The server returns the client payload as-is. + rpc UnaryCall(SimpleRequest) returns (SimpleResponse); + + // One request followed by a sequence of responses (streamed download). + // The server returns the payload with client desired type and sizes. + rpc StreamingOutputCall(StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); + + // A sequence of requests followed by one response (streamed upload). + // The server returns the aggregated size of client payload as the result. + rpc StreamingInputCall(stream StreamingInputCallRequest) + returns (StreamingInputCallResponse); + + // A sequence of requests with each request served by the server immediately. + // As one request could lead to multiple responses, this interface + // demonstrates the idea of full bidirectionality. + rpc FullBidiCall(stream StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); + + // A sequence of requests followed by a sequence of responses. + // The server buffers all the client requests and then serves them in order. A + // stream of responses are returned to the client when the server starts with + // first request. + rpc HalfBidiCall(stream StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); +} diff --git a/protobuf-lite/build.gradle b/protobuf-lite/build.gradle new file mode 100644 index 000000000..1425a3bb8 --- /dev/null +++ b/protobuf-lite/build.gradle @@ -0,0 +1,15 @@ +plugins { + id "be.insaneprogramming.gradle.animalsniffer" version "1.4.0" +} + +description = 'gRPC: Protobuf Lite' + +dependencies { + compile project(':grpc-core'), + libraries.protobuf, + libraries.guava +} + +animalsniffer { + signature = "org.codehaus.mojo.signature:java16:+@signature" +} diff --git a/protobuf/src/main/java/io/grpc/protobuf/ProtoInputStream.java b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoInputStream.java index 186e3c12e..b2e4282f5 100644 --- a/protobuf/src/main/java/io/grpc/protobuf/ProtoInputStream.java +++ b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoInputStream.java @@ -29,7 +29,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package io.grpc.protobuf; +package io.grpc.protobuf.lite; import com.google.common.io.ByteStreams; import com.google.protobuf.CodedOutputStream; diff --git a/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java new file mode 100644 index 000000000..c5677ff5e --- /dev/null +++ b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java @@ -0,0 +1,136 @@ +/* + * Copyright 2014, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.grpc.protobuf.lite; + +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.MessageLite; +import com.google.protobuf.Parser; + +import io.grpc.ExperimentalApi; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor.Marshaller; +import io.grpc.Status; + +import java.io.InputStream; + +/** + * Utility methods for using protobuf with grpc. + */ +@ExperimentalApi("Experimental until Lite is stable in protobuf") +public class ProtoLiteUtils { + + /** Create a {@code Marshaller} for protos of the same type as {@code defaultInstance}. */ + public static <T extends MessageLite> Marshaller<T> marshaller(final T defaultInstance) { + @SuppressWarnings("unchecked") + final Parser<T> parser = (Parser<T>) defaultInstance.getParserForType(); + return new Marshaller<T>() { + @Override + public InputStream stream(T value) { + return new ProtoInputStream(value, parser); + } + + @Override + public T parse(InputStream stream) { + if (stream instanceof ProtoInputStream) { + ProtoInputStream protoStream = (ProtoInputStream) stream; + // Optimization for in-memory transport. Returning provided object is safe since protobufs + // are immutable. + // + // However, we can't assume the types match, so we have to verify the parser matches. + // Today the parser is always the same for a given proto, but that isn't guaranteed. Even + // if not, using the same MethodDescriptor would ensure the parser matches and permit us + // to enable this optimization. + if (protoStream.parser() == parser) { + try { + @SuppressWarnings("unchecked") + T message = (T) ((ProtoInputStream) stream).message(); + return message; + } catch (IllegalStateException ex) { + // Stream must have been read from, which is a strange state. Since the point of this + // optimization is to be transparent, instead of throwing an error we'll continue, + // even though it seems likely there's a bug. + } + } + } + try { + return parseFrom(stream); + } catch (InvalidProtocolBufferException ipbe) { + throw Status.INTERNAL.withDescription("Invalid protobuf byte sequence") + .withCause(ipbe).asRuntimeException(); + } + } + + private T parseFrom(InputStream stream) throws InvalidProtocolBufferException { + // Pre-create the CodedInputStream so that we can remove the size limit restriction + // when parsing. + CodedInputStream codedInput = CodedInputStream.newInstance(stream); + codedInput.setSizeLimit(Integer.MAX_VALUE); + + T message = parser.parseFrom(codedInput); + try { + codedInput.checkLastTagWas(0); + return message; + } catch (InvalidProtocolBufferException e) { + e.setUnfinishedMessage(message); + throw e; + } + } + }; + } + + /** + * Produce a metadata marshaller for a protobuf type. + */ + public static <T extends MessageLite> Metadata.BinaryMarshaller<T> metadataMarshaller( + final T instance) { + return new Metadata.BinaryMarshaller<T>() { + @Override + public byte[] toBytes(T value) { + return value.toByteArray(); + } + + @Override + @SuppressWarnings("unchecked") + public T parseBytes(byte[] serialized) { + try { + return (T) instance.getParserForType().parseFrom(serialized); + } catch (InvalidProtocolBufferException ipbe) { + throw new IllegalArgumentException(ipbe); + } + } + }; + } + + private ProtoLiteUtils() { + } +} diff --git a/protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java b/protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java new file mode 100644 index 000000000..a5042a435 --- /dev/null +++ b/protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java @@ -0,0 +1,214 @@ +/* + * Copyright 2015, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.grpc.protobuf.lite; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; + +import com.google.common.io.ByteStreams; +import com.google.protobuf.ByteString; +import com.google.protobuf.Empty; +import com.google.protobuf.Enum; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Type; + +import io.grpc.Drainable; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor.Marshaller; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +/** Unit tests for {@link ProtoLiteUtils}. */ +@RunWith(JUnit4.class) +public class ProtoLiteUtilsTest { + private Marshaller<Type> marshaller = ProtoLiteUtils.marshaller(Type.getDefaultInstance()); + private Type proto = Type.newBuilder().setName("name").build(); + + @Test + public void testPassthrough() { + assertSame(proto, marshaller.parse(marshaller.stream(proto))); + } + + @Test + public void testRoundtrip() throws Exception { + InputStream is = marshaller.stream(proto); + is = new ByteArrayInputStream(ByteStreams.toByteArray(is)); + assertEquals(proto, marshaller.parse(is)); + } + + @Test + public void testInvalidatedMessage() throws Exception { + InputStream is = marshaller.stream(proto); + // Invalidates message, and drains all bytes + ByteStreams.toByteArray(is); + try { + ((ProtoInputStream) is).message(); + fail("Expected exception"); + } catch (IllegalStateException ex) { + // expected + } + // Zero bytes is the default message + assertEquals(Type.getDefaultInstance(), marshaller.parse(is)); + } + + @Test + public void parseInvalid() throws Exception { + InputStream is = new ByteArrayInputStream(new byte[] {-127}); + try { + marshaller.parse(is); + fail("Expected exception"); + } catch (StatusRuntimeException ex) { + assertEquals(Status.Code.INTERNAL, ex.getStatus().getCode()); + assertNotNull(((InvalidProtocolBufferException) ex.getCause()).getUnfinishedMessage()); + } + } + + @Test + public void testMismatch() throws Exception { + Marshaller<Enum> enumMarshaller = ProtoLiteUtils.marshaller(Enum.getDefaultInstance()); + // Enum's name and Type's name are both strings with tag 1. + Enum altProto = Enum.newBuilder().setName(proto.getName()).build(); + assertEquals(proto, marshaller.parse(enumMarshaller.stream(altProto))); + } + + @Test + public void marshallerShouldNotLimitProtoSize() throws Exception { + // The default limit is 64MB. Using a larger proto to verify that the limit is not enforced. + byte[] bigName = new byte[70 * 1024 * 1024]; + Arrays.fill(bigName, (byte) 32); + + proto = Type.newBuilder().setNameBytes(ByteString.copyFrom(bigName)).build(); + + // Just perform a round trip to verify that it works. + testRoundtrip(); + } + + @Test + public void testAvailable() throws Exception { + InputStream is = marshaller.stream(proto); + assertEquals(proto.getSerializedSize(), is.available()); + is.read(); + assertEquals(proto.getSerializedSize() - 1, is.available()); + while (is.read() != -1) {} + assertEquals(-1, is.read()); + assertEquals(0, is.available()); + } + + @Test + public void testEmpty() throws IOException { + Marshaller<Empty> marshaller = ProtoLiteUtils.marshaller(Empty.getDefaultInstance()); + InputStream is = marshaller.stream(Empty.getDefaultInstance()); + assertEquals(0, is.available()); + byte[] b = new byte[10]; + assertEquals(-1, is.read(b)); + assertArrayEquals(new byte[10], b); + // Do the same thing again, because the internal state may be different + assertEquals(-1, is.read(b)); + assertArrayEquals(new byte[10], b); + assertEquals(-1, is.read()); + assertEquals(0, is.available()); + } + + @Test + public void testDrainTo_all() throws Exception { + byte[] golden = ByteStreams.toByteArray(marshaller.stream(proto)); + InputStream is = marshaller.stream(proto); + Drainable d = (Drainable) is; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int drained = d.drainTo(baos); + assertEquals(baos.size(), drained); + assertArrayEquals(golden, baos.toByteArray()); + assertEquals(0, is.available()); + } + + @Test + public void testDrainTo_partial() throws Exception { + final byte[] golden; + { + InputStream is = marshaller.stream(proto); + is.read(); + golden = ByteStreams.toByteArray(is); + } + InputStream is = marshaller.stream(proto); + is.read(); + Drainable d = (Drainable) is; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int drained = d.drainTo(baos); + assertEquals(baos.size(), drained); + assertArrayEquals(golden, baos.toByteArray()); + assertEquals(0, is.available()); + } + + @Test + public void testDrainTo_none() throws Exception { + byte[] golden = ByteStreams.toByteArray(marshaller.stream(proto)); + InputStream is = marshaller.stream(proto); + ByteStreams.toByteArray(is); + Drainable d = (Drainable) is; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + assertEquals(0, d.drainTo(baos)); + assertArrayEquals(new byte[0], baos.toByteArray()); + assertEquals(0, is.available()); + } + + @Test + public void metadataMarshaller_roundtrip() { + Metadata.BinaryMarshaller<Type> metadataMarshaller = + ProtoLiteUtils.metadataMarshaller(Type.getDefaultInstance()); + assertEquals(proto, metadataMarshaller.parseBytes(metadataMarshaller.toBytes(proto))); + } + + @Test + public void metadataMarshaller_invalid() { + Metadata.BinaryMarshaller<Type> metadataMarshaller = + ProtoLiteUtils.metadataMarshaller(Type.getDefaultInstance()); + try { + metadataMarshaller.parseBytes(new byte[] {-127}); + fail("Expected exception"); + } catch (IllegalArgumentException ex) { + assertNotNull(((InvalidProtocolBufferException) ex.getCause()).getUnfinishedMessage()); + } + } +} diff --git a/protobuf/build.gradle b/protobuf/build.gradle index 89da4b023..6b54173eb 100644 --- a/protobuf/build.gradle +++ b/protobuf/build.gradle @@ -6,6 +6,7 @@ description = 'gRPC: Protobuf' dependencies { compile project(':grpc-core'), + project(':grpc-protobuf-lite'), libraries.protobuf, libraries.guava, libraries.protobuf_util diff --git a/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java b/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java index ae8f0013e..48cb6e1d7 100644 --- a/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java +++ b/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java @@ -31,13 +31,10 @@ package io.grpc.protobuf; -import com.google.common.annotations.VisibleForTesting; -import com.google.protobuf.CodedInputStream; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; import com.google.protobuf.Message.Builder; import com.google.protobuf.MessageLite; -import com.google.protobuf.Parser; import com.google.protobuf.util.JsonFormat; import com.google.protobuf.util.JsonFormat.Printer; @@ -45,6 +42,7 @@ import io.grpc.ExperimentalApi; import io.grpc.Metadata; import io.grpc.MethodDescriptor.Marshaller; import io.grpc.Status; +import io.grpc.protobuf.lite.ProtoLiteUtils; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -58,63 +56,19 @@ import java.nio.charset.Charset; */ public class ProtoUtils { - /** Create a {@code Marshaller} for protos of the same type as {@code defaultInstance}. */ + /** + * Create a {@code Marshaller} for protos of the same type as {@code defaultInstance}. + * + * @deprecated Use ProtoLiteUtils.marshaller() or Message-based marshaller() instead + */ + @Deprecated public static <T extends MessageLite> Marshaller<T> marshaller(final T defaultInstance) { - @SuppressWarnings("unchecked") - final Parser<T> parser = (Parser<T>) defaultInstance.getParserForType(); - return new Marshaller<T>() { - @Override - public InputStream stream(T value) { - return new ProtoInputStream(value, parser); - } - - @Override - public T parse(InputStream stream) { - if (stream instanceof ProtoInputStream) { - ProtoInputStream protoStream = (ProtoInputStream) stream; - // Optimization for in-memory transport. Returning provided object is safe since protobufs - // are immutable. - // - // However, we can't assume the types match, so we have to verify the parser matches. - // Today the parser is always the same for a given proto, but that isn't guaranteed. Even - // if not, using the same MethodDescriptor would ensure the parser matches and permit us - // to enable this optimization. - if (protoStream.parser() == parser) { - try { - @SuppressWarnings("unchecked") - T message = (T) ((ProtoInputStream) stream).message(); - return message; - } catch (IllegalStateException ex) { - // Stream must have been read from, which is a strange state. Since the point of this - // optimization is to be transparent, instead of throwing an error we'll continue, - // even though it seems likely there's a bug. - } - } - } - try { - return parseFrom(stream); - } catch (InvalidProtocolBufferException ipbe) { - throw Status.INTERNAL.withDescription("Invalid protobuf byte sequence") - .withCause(ipbe).asRuntimeException(); - } - } - - private T parseFrom(InputStream stream) throws InvalidProtocolBufferException { - // Pre-create the CodedInputStream so that we can remove the size limit restriction - // when parsing. - CodedInputStream codedInput = CodedInputStream.newInstance(stream); - codedInput.setSizeLimit(Integer.MAX_VALUE); + return ProtoLiteUtils.marshaller(defaultInstance); + } - T message = parser.parseFrom(codedInput); - try { - codedInput.checkLastTagWas(0); - return message; - } catch (InvalidProtocolBufferException e) { - e.setUnfinishedMessage(message); - throw e; - } - } - }; + /** Create a {@code Marshaller} for protos of the same type as {@code defaultInstance}. */ + public static <T extends Message> Marshaller<T> marshaller(final T defaultInstance) { + return ProtoLiteUtils.marshaller(defaultInstance); } /** @@ -170,27 +124,7 @@ public class ProtoUtils { public static <T extends Message> Metadata.Key<T> keyForProto(T instance) { return Metadata.Key.of( instance.getDescriptorForType().getFullName() + Metadata.BINARY_HEADER_SUFFIX, - keyMarshaller(instance)); - } - - @VisibleForTesting - static <T extends MessageLite> Metadata.BinaryMarshaller<T> keyMarshaller(final T instance) { - return new Metadata.BinaryMarshaller<T>() { - @Override - public byte[] toBytes(T value) { - return value.toByteArray(); - } - - @Override - @SuppressWarnings("unchecked") - public T parseBytes(byte[] serialized) { - try { - return (T) instance.getParserForType().parseFrom(serialized); - } catch (InvalidProtocolBufferException ipbe) { - throw new IllegalArgumentException(ipbe); - } - } - }; + ProtoLiteUtils.metadataMarshaller(instance)); } private ProtoUtils() { diff --git a/protobuf/src/test/java/io/grpc/protobuf/ProtoUtilsTest.java b/protobuf/src/test/java/io/grpc/protobuf/ProtoUtilsTest.java index dc9ede460..8281da89c 100644 --- a/protobuf/src/test/java/io/grpc/protobuf/ProtoUtilsTest.java +++ b/protobuf/src/test/java/io/grpc/protobuf/ProtoUtilsTest.java @@ -31,166 +31,42 @@ package io.grpc.protobuf; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.fail; import com.google.common.io.ByteStreams; -import com.google.protobuf.ByteString; -import com.google.protobuf.Empty; -import com.google.protobuf.Enum; -import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.MessageLite; import com.google.protobuf.Type; -import io.grpc.Drainable; -import io.grpc.Metadata; import io.grpc.MethodDescriptor.Marshaller; -import io.grpc.Status; -import io.grpc.StatusRuntimeException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; /** Unit tests for {@link ProtoUtils}. */ @RunWith(JUnit4.class) public class ProtoUtilsTest { - private Marshaller<Type> marshaller = ProtoUtils.marshaller(Type.getDefaultInstance()); private Type proto = Type.newBuilder().setName("name").build(); @Test - public void testPassthrough() { - assertSame(proto, marshaller.parse(marshaller.stream(proto))); - } - - @Test public void testRoundtrip() throws Exception { + Marshaller<Type> marshaller = ProtoUtils.marshaller(Type.getDefaultInstance()); InputStream is = marshaller.stream(proto); is = new ByteArrayInputStream(ByteStreams.toByteArray(is)); assertEquals(proto, marshaller.parse(is)); } @Test - public void testInvalidatedMessage() throws Exception { - InputStream is = marshaller.stream(proto); - // Invalidates message, and drains all bytes - ByteStreams.toByteArray(is); - try { - ((ProtoInputStream) is).message(); - fail("Expected exception"); - } catch (IllegalStateException ex) { - // expected - } - // Zero bytes is the default message - assertEquals(Type.getDefaultInstance(), marshaller.parse(is)); - } - - @Test - public void parseInvalid() throws Exception { - InputStream is = new ByteArrayInputStream(new byte[] {-127}); - try { - marshaller.parse(is); - fail("Expected exception"); - } catch (StatusRuntimeException ex) { - assertEquals(Status.Code.INTERNAL, ex.getStatus().getCode()); - assertNotNull(((InvalidProtocolBufferException) ex.getCause()).getUnfinishedMessage()); - } - } - - @Test - public void testMismatch() throws Exception { - Marshaller<Enum> enumMarshaller = ProtoUtils.marshaller(Enum.getDefaultInstance()); - // Enum's name and Type's name are both strings with tag 1. - Enum altProto = Enum.newBuilder().setName(proto.getName()).build(); - assertEquals(proto, marshaller.parse(enumMarshaller.stream(altProto))); - } - - @Test - public void marshallerShouldNotLimitProtoSize() throws Exception { - // The default limit is 64MB. Using a larger proto to verify that the limit is not enforced. - byte[] bigName = new byte[70 * 1024 * 1024]; - Arrays.fill(bigName, (byte) 32); - - proto = Type.newBuilder().setNameBytes(ByteString.copyFrom(bigName)).build(); - - // Just perform a round trip to verify that it works. - testRoundtrip(); - } - - @Test - public void testAvailable() throws Exception { + @Deprecated + public void testRoundtripLite() throws Exception { + Marshaller<MessageLite> marshaller + = ProtoUtils.marshaller((MessageLite) Type.getDefaultInstance()); InputStream is = marshaller.stream(proto); - assertEquals(proto.getSerializedSize(), is.available()); - is.read(); - assertEquals(proto.getSerializedSize() - 1, is.available()); - while (is.read() != -1) {} - assertEquals(-1, is.read()); - assertEquals(0, is.available()); - } - - @Test - public void testEmpty() throws IOException { - Marshaller<Empty> marshaller = ProtoUtils.marshaller(Empty.getDefaultInstance()); - InputStream is = marshaller.stream(Empty.getDefaultInstance()); - assertEquals(0, is.available()); - byte[] b = new byte[10]; - assertEquals(-1, is.read(b)); - assertArrayEquals(new byte[10], b); - // Do the same thing again, because the internal state may be different - assertEquals(-1, is.read(b)); - assertArrayEquals(new byte[10], b); - assertEquals(-1, is.read()); - assertEquals(0, is.available()); - } - - @Test - public void testDrainTo_all() throws Exception { - byte[] golden = ByteStreams.toByteArray(marshaller.stream(proto)); - InputStream is = marshaller.stream(proto); - Drainable d = (Drainable) is; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int drained = d.drainTo(baos); - assertEquals(baos.size(), drained); - assertArrayEquals(golden, baos.toByteArray()); - assertEquals(0, is.available()); - } - - @Test - public void testDrainTo_partial() throws Exception { - final byte[] golden; - { - InputStream is = marshaller.stream(proto); - is.read(); - golden = ByteStreams.toByteArray(is); - } - InputStream is = marshaller.stream(proto); - is.read(); - Drainable d = (Drainable) is; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int drained = d.drainTo(baos); - assertEquals(baos.size(), drained); - assertArrayEquals(golden, baos.toByteArray()); - assertEquals(0, is.available()); - } - - @Test - public void testDrainTo_none() throws Exception { - byte[] golden = ByteStreams.toByteArray(marshaller.stream(proto)); - InputStream is = marshaller.stream(proto); - ByteStreams.toByteArray(is); - Drainable d = (Drainable) is; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - assertEquals(0, d.drainTo(baos)); - assertArrayEquals(new byte[0], baos.toByteArray()); - assertEquals(0, is.available()); + is = new ByteArrayInputStream(ByteStreams.toByteArray(is)); + assertEquals(proto, marshaller.parse(is)); } @Test @@ -198,25 +74,4 @@ public class ProtoUtilsTest { assertEquals("google.protobuf.Type-bin", ProtoUtils.keyForProto(Type.getDefaultInstance()).originalName()); } - - @Test - public void keyMarshaller_roundtrip() { - Metadata.BinaryMarshaller<Type> keyMarshaller = - ProtoUtils.keyMarshaller(Type.getDefaultInstance()); - assertEquals(proto, keyMarshaller.parseBytes(keyMarshaller.toBytes(proto))); - } - - @Test - public void keyMarshaller_invalid() { - Metadata.BinaryMarshaller<Type> keyMarshaller = - ProtoUtils.keyMarshaller(Type.getDefaultInstance()); - try { - keyMarshaller.parseBytes(new byte[] {-127}); - fail("Expected exception"); - } catch (IllegalArgumentException ex) { - assertNotNull(((InvalidProtocolBufferException) ex.getCause()).getUnfinishedMessage()); - } - } - - } diff --git a/settings.gradle b/settings.gradle index f235a3351..9bed7a107 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ include ":grpc-stub" include ":grpc-auth" include ":grpc-okhttp" include ":grpc-protobuf" +include ":grpc-protobuf-lite" include ":grpc-protobuf-nano" include ":grpc-netty" include ":grpc-grpclb" @@ -18,6 +19,7 @@ project(':grpc-stub').projectDir = "$rootDir/stub" as File project(':grpc-auth').projectDir = "$rootDir/auth" as File project(':grpc-okhttp').projectDir = "$rootDir/okhttp" as File project(':grpc-protobuf').projectDir = "$rootDir/protobuf" as File +project(':grpc-protobuf-lite').projectDir = "$rootDir/protobuf-lite" as File project(':grpc-protobuf-nano').projectDir = "$rootDir/protobuf-nano" as File project(':grpc-netty').projectDir = "$rootDir/netty" as File project(':grpc-grpclb').projectDir = "$rootDir/grpclb" as File |