Skip to content

Commit

Permalink
Support parsing custom REST paths from proto files and generating @ma… (
Browse files Browse the repository at this point in the history
  • Loading branch information
heliang666s authored Nov 3, 2024
1 parent 3e99823 commit 9741de3
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 96 deletions.
1 change: 0 additions & 1 deletion dubbo-plugin/dubbo-compiler/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,4 @@
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
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.api.HttpRule.PatternCase;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
Expand All @@ -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 {
Expand All @@ -67,24 +71,40 @@ protected String getInterfaceTemplateFileName() {
}

private String getServiceJavaDocPrefix() {
return " ";
return "";
}

private String getMethodJavaDocPrefix() {
return " ";
return " ";
}

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<>();

Expand Down Expand Up @@ -171,6 +191,40 @@ private MethodContext buildMethodContext(
methodContext.isManyOutput = methodProto.getServerStreaming();
methodContext.methodNumber = methodNumber;

// compile google.api.http option
HttpRule httpRule = parseHttpRule(methodProto);
if (httpRule != null) {
PatternCase patternCase = httpRule.getPatternCase();
String path;
switch (patternCase) {
case GET:
path = httpRule.getGet();
break;
case PUT:
path = httpRule.getPut();
break;
case POST:
path = httpRule.getPost();
break;
case DELETE:
path = httpRule.getDelete();
break;
case PATCH:
path = httpRule.getPatch();
break;
default:
path = "";
break;
}
if (!path.isEmpty()) {
methodContext.httpMethod = patternCase.name();
methodContext.path = path;
methodContext.body = httpRule.getBody();
methodContext.hasMapping = true;
methodContext.needRequestAnnotation = !methodContext.body.isEmpty() || path.contains("{");
}
}

Location methodLocation = locations.stream()
.filter(location -> location.getPathCount() == METHOD_NUMBER_OF_PATHS
&& location.getPath(METHOD_NUMBER_OF_PATHS - 1) == methodNumber)
Expand All @@ -197,6 +251,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);
}
Expand Down Expand Up @@ -360,6 +425,26 @@ private static class MethodContext {
public String grpcCallsMethodName;
public int methodNumber;
public String javaDoc;
/**
* The HTTP request method
*/
public String httpMethod;
/**
* The HTTP request path
*/
public String path;
/**
* The message field that the HTTP request body mapping to
*/
public String body;
/**
* Whether the method has HTTP mapping
*/
public boolean hasMapping;
/**
* Whether the request body parameter need @GRequest annotation
*/
public boolean needRequestAnnotation;

// 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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

{{#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;
Expand All @@ -28,7 +31,6 @@ import java.util.function.BiConsumer;
import java.util.concurrent.CompletableFuture;

public interface {{interfaceClassName}} extends org.apache.dubbo.rpc.model.DubboStub {
String JAVA_SERVICE_NAME = "{{packageName}}.{{serviceName}}";
{{#commonPackageName}}
String SERVICE_NAME = "{{commonPackageName}}.{{serviceName}}";
Expand All @@ -40,34 +42,32 @@ public interface {{interfaceClassName}} extends org.apache.dubbo.rpc.model.Dubbo
{{#javaDoc}}
{{{javaDoc}}}
{{/javaDoc}}
{{outputType}} {{methodName}}({{inputType}} request);

CompletableFuture<{{outputType}}> {{methodName}}Async({{inputType}} request);


{{#hasMapping}}
@Mapping(method = HttpMethods.{{httpMethod}}, path = "{{path}}")
{{/hasMapping}}
{{outputType}} {{methodName}}({{#needRequestAnnotation}}@GRequest{{#body}}("{{body}}"){{/body}} {{/needRequestAnnotation}}{{inputType}} request);

CompletableFuture<{{outputType}}> {{methodName}}Async({{#needRequestAnnotation}}@GRequest {{/needRequestAnnotation}}{{inputType}} request);
{{/unaryMethods}}

{{#serverStreamingMethods}}
{{#javaDoc}}
{{{javaDoc}}}
{{/javaDoc}}
void {{methodName}}({{inputType}} request, StreamObserver<{{outputType}}> responseObserver);
{{#hasMapping}}
@Mapping(method = HttpMethods.{{httpMethod}}, path = "{{path}}")
{{/hasMapping}}
void {{methodName}}({{#needRequestAnnotation}}@GRequest{{#body}}("{{body}}"){{/body}} {{/needRequestAnnotation}}{{inputType}} request, StreamObserver<{{outputType}}> responseObserver);
{{/serverStreamingMethods}}

{{#biStreamingWithoutClientStreamMethods}}
{{#javaDoc}}
{{{javaDoc}}}
{{/javaDoc}}
StreamObserver<{{inputType}}> {{methodName}}(StreamObserver<{{outputType}}> responseObserver);
{{/biStreamingWithoutClientStreamMethods}}


{{#clientStreamingMethods}}
{{#javaDoc}}
{{{javaDoc}}}
{{/javaDoc}}
StreamObserver<{{inputType}}> {{methodName}}(StreamObserver<{{outputType}}> responseObserver);
{{/clientStreamingMethods}}

}
Loading

0 comments on commit 9741de3

Please sign in to comment.