-
Notifications
You must be signed in to change notification settings - Fork 26.6k
Support parsing custom REST paths from proto files and generating @Ma… #14796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
207acd9
1a1b134
cad87be
cff30c8
2d5e9e0
1558e14
c8ffb11
78db13a
3229941
d138109
9e27114
fdb5047
2b69646
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
| package org.apache.dubbo.gen; | ||
|
|
||
| import org.apache.dubbo.gen.utils.ProtoTypeMap; | ||
| import org.apache.dubbo.remoting.http12.HttpMethods; | ||
|
|
||
| import javax.annotation.Nonnull; | ||
|
|
||
|
|
@@ -33,6 +34,8 @@ | |
| import com.github.mustachejava.DefaultMustacheFactory; | ||
| import com.github.mustachejava.Mustache; | ||
| import com.github.mustachejava.MustacheFactory; | ||
| import com.google.api.AnnotationsProto; | ||
| import com.google.api.HttpRule; | ||
| import com.google.common.base.Charsets; | ||
| import com.google.common.base.Preconditions; | ||
| import com.google.common.base.Strings; | ||
|
|
@@ -42,6 +45,7 @@ | |
| import com.google.protobuf.DescriptorProtos.MethodDescriptorProto; | ||
| import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto; | ||
| import com.google.protobuf.DescriptorProtos.SourceCodeInfo.Location; | ||
| import com.google.protobuf.ExtensionRegistry; | ||
| import com.google.protobuf.compiler.PluginProtos; | ||
|
|
||
| public abstract class AbstractGenerator { | ||
|
|
@@ -75,16 +79,32 @@ private String getMethodJavaDocPrefix() { | |
| } | ||
|
|
||
| public List<PluginProtos.CodeGeneratorResponse.File> generateFiles(PluginProtos.CodeGeneratorRequest request) { | ||
| final ProtoTypeMap typeMap = ProtoTypeMap.of(request.getProtoFileList()); | ||
| // 1. build ExtensionRegistry and registry | ||
| ExtensionRegistry registry = ExtensionRegistry.newInstance(); | ||
| AnnotationsProto.registerAllExtensions(registry); | ||
|
|
||
| // 2. compile proto file | ||
| List<FileDescriptorProto> protosToGenerate = request.getProtoFileList().stream() | ||
| .filter(protoFile -> request.getFileToGenerateList().contains(protoFile.getName())) | ||
| .map(protoFile -> parseWithExtensions(protoFile, registry)) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| // 3. use compiled proto file build ProtoTypeMap | ||
| final ProtoTypeMap typeMap = ProtoTypeMap.of(protosToGenerate); | ||
|
|
||
| // 4. find and generate serviceContext | ||
| List<ServiceContext> services = findServices(protosToGenerate, typeMap); | ||
| return generateFiles(services); | ||
| } | ||
|
|
||
| private FileDescriptorProto parseWithExtensions(FileDescriptorProto protoFile, ExtensionRegistry registry) { | ||
| try { | ||
| return FileDescriptorProto.parseFrom(protoFile.toByteString(), registry); | ||
| } catch (Exception e) { | ||
| return protoFile; | ||
| } | ||
| } | ||
|
|
||
| private List<ServiceContext> findServices(List<FileDescriptorProto> protos, ProtoTypeMap typeMap) { | ||
| List<ServiceContext> contexts = new ArrayList<>(); | ||
|
|
||
|
|
@@ -171,6 +191,43 @@ private MethodContext buildMethodContext( | |
| methodContext.isManyOutput = methodProto.getServerStreaming(); | ||
| methodContext.methodNumber = methodNumber; | ||
|
|
||
| // compile google.api.http option | ||
| HttpRule httpRule = parseHttpRule(methodProto); | ||
| if (httpRule != null) { | ||
| methodContext.hasMappings = true; | ||
| if (!httpRule.getGet().isEmpty()) { | ||
| methodContext.httpMethod = HttpMethods.GET; | ||
| } else if (!httpRule.getPost().isEmpty()) { | ||
| methodContext.httpMethod = HttpMethods.POST; | ||
| } else if (!httpRule.getPut().isEmpty()) { | ||
| methodContext.httpMethod = HttpMethods.PUT; | ||
| } else if (!httpRule.getDelete().isEmpty()) { | ||
| methodContext.httpMethod = HttpMethods.DELETE; | ||
| } else { | ||
| methodContext.httpMethod = null; | ||
| } | ||
|
|
||
| if (!httpRule.getGet().isEmpty()) { | ||
| methodContext.path = httpRule.getGet(); | ||
|
||
| } else if (!httpRule.getPost().isEmpty()) { | ||
| methodContext.path = httpRule.getPost(); | ||
| } else if (!httpRule.getPut().isEmpty()) { | ||
| methodContext.path = httpRule.getPut(); | ||
| } else if (!httpRule.getDelete().isEmpty()) { | ||
| methodContext.path = httpRule.getDelete(); | ||
| } else { | ||
| methodContext.path = ""; | ||
| } | ||
|
|
||
| methodContext.body = httpRule.getBody(); | ||
| } else { | ||
| methodContext.httpMethod = null; | ||
| methodContext.path = ""; | ||
| methodContext.body = ""; | ||
| } | ||
|
|
||
| methodContext.hasBody = !Strings.isNullOrEmpty(methodContext.body); | ||
|
|
||
| Location methodLocation = locations.stream() | ||
| .filter(location -> location.getPathCount() == METHOD_NUMBER_OF_PATHS | ||
| && location.getPath(METHOD_NUMBER_OF_PATHS - 1) == methodNumber) | ||
|
|
@@ -197,6 +254,17 @@ private MethodContext buildMethodContext( | |
| return methodContext; | ||
| } | ||
|
|
||
| private HttpRule parseHttpRule(MethodDescriptorProto methodProto) { | ||
| HttpRule rule = null; | ||
| // check methodProto have options | ||
| if (methodProto.hasOptions()) { | ||
| if (methodProto.getOptions().hasExtension(AnnotationsProto.http)) { | ||
| rule = methodProto.getOptions().getExtension(AnnotationsProto.http); | ||
| } | ||
| } | ||
| return rule; | ||
| } | ||
|
|
||
| private String lowerCaseFirst(String s) { | ||
| return Character.toLowerCase(s.charAt(0)) + s.substring(1); | ||
| } | ||
|
|
@@ -360,6 +428,11 @@ private static class MethodContext { | |
| public String grpcCallsMethodName; | ||
| public int methodNumber; | ||
| public String javaDoc; | ||
| public HttpMethods httpMethod; | ||
| public String path; | ||
| public String body; // requestBody | ||
|
||
| public boolean hasMappings; | ||
| public boolean hasBody; | ||
|
|
||
| // This method mimics the upper-casing method ogf gRPC to ensure compatibility | ||
| // See https://github.com/grpc/grpc-java/blob/v1.8.0/compiler/src/java_plugin/cpp/java_generator.cpp#L58 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,10 +16,14 @@ | |
| */ | ||
|
|
||
| {{#packageName}} | ||
| package {{packageName}}; | ||
| package {{packageName}}; | ||
|
||
| {{/packageName}} | ||
|
|
||
| import org.apache.dubbo.common.stream.StreamObserver; | ||
| import org.apache.dubbo.remoting.http12.HttpMethods; | ||
| import org.apache.dubbo.remoting.http12.rest.Mapping; | ||
| import org.apache.dubbo.rpc.stub.annotations.Grequest; | ||
|
|
||
| import com.google.protobuf.Message; | ||
|
|
||
| import java.util.HashMap; | ||
|
|
@@ -29,44 +33,58 @@ import java.util.concurrent.CompletableFuture; | |
|
|
||
| public interface {{interfaceClassName}} extends org.apache.dubbo.rpc.model.DubboStub { | ||
|
|
||
| String JAVA_SERVICE_NAME = "{{packageName}}.{{serviceName}}"; | ||
| String JAVA_SERVICE_NAME = "{{packageName}}.{{serviceName}}"; | ||
| {{#commonPackageName}} | ||
| String SERVICE_NAME = "{{commonPackageName}}.{{serviceName}}"; | ||
| {{/commonPackageName}} | ||
| {{^commonPackageName}} | ||
| String SERVICE_NAME = "{{serviceName}}"; | ||
| {{/commonPackageName}} | ||
|
|
||
| {{#unaryMethods}} | ||
| {{#javaDoc}} | ||
| {{{javaDoc}}} | ||
| {{{javaDoc}}} | ||
| {{/javaDoc}} | ||
| {{outputType}} {{methodName}}({{inputType}} request); | ||
|
|
||
| CompletableFuture<{{outputType}}> {{methodName}}Async({{inputType}} request); | ||
|
|
||
| {{#hasMappings}} | ||
| @Mapping(method = HttpMethods.{{httpMethod}}, path = "{{path}}") | ||
|
||
| {{/hasMappings}} | ||
| public {{outputType}} {{methodName}}({{#hasBody}}@Grequest {{/hasBody}}{{inputType}} request); | ||
|
|
||
| CompletableFuture<{{outputType}}> {{methodName}}Async({{#hasBody}}@Grequest {{/hasBody}}{{inputType}} request); | ||
|
|
||
| {{/unaryMethods}} | ||
|
|
||
| {{#serverStreamingMethods}} | ||
| {{#javaDoc}} | ||
| {{{javaDoc}}} | ||
| {{{javaDoc}}} | ||
| {{/javaDoc}} | ||
| void {{methodName}}({{inputType}} request, StreamObserver<{{outputType}}> responseObserver); | ||
|
|
||
| {{#hasMappings}} | ||
| @Mapping(method = HttpMethods.{{httpMethod}}, uri = "{{uri}}") | ||
| {{/hasMappings}} | ||
| void {{methodName}}({{#hasBody}}@Grequest{{/hasBody}}{{inputType}} request, StreamObserver<{{outputType}}> responseObserver); | ||
| {{/serverStreamingMethods}} | ||
|
|
||
| {{#biStreamingWithoutClientStreamMethods}} | ||
| {{#javaDoc}} | ||
| {{{javaDoc}}} | ||
| {{{javaDoc}}} | ||
| {{/javaDoc}} | ||
|
|
||
| {{#hasMappings}} | ||
| @Mapping(method = HttpMethods.{{httpMethod}}, uri = "{{uri}}") | ||
| {{/hasMappings}} | ||
| StreamObserver<{{inputType}}> {{methodName}}(StreamObserver<{{outputType}}> responseObserver); | ||
| {{/biStreamingWithoutClientStreamMethods}} | ||
|
|
||
|
|
||
| {{#clientStreamingMethods}} | ||
| {{#javaDoc}} | ||
| {{{javaDoc}}} | ||
| {{{javaDoc}}} | ||
| {{/javaDoc}} | ||
|
|
||
| {{#hasMappings}} | ||
| @Mapping(method = HttpMethods.{{httpMethod}}, uri = "{{uri}}") | ||
| {{/hasMappings}} | ||
| StreamObserver<{{inputType}}> {{methodName}}(StreamObserver<{{outputType}}> responseObserver); | ||
| {{/clientStreamingMethods}} | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this module just to generate code?