Skip to content

Commit aabeebe

Browse files
committed
Implement JAX-RS service generation
1 parent 8484dab commit aabeebe

16 files changed

+601
-2
lines changed

plugin/src/main/java/com/flit/protoc/Plugin.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.flit.protoc.gen.Generator;
44
import com.flit.protoc.gen.GeneratorException;
5+
import com.flit.protoc.gen.server.jaxrs.JaxrsGenerator;
56
import com.flit.protoc.gen.server.spring.SpringGenerator;
67
import com.flit.protoc.gen.server.undertow.UndertowGenerator;
78
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest;
@@ -22,7 +23,7 @@ public Plugin(CodeGeneratorRequest request) {
2223

2324
public CodeGeneratorResponse process() {
2425
if (!request.hasParameter()) {
25-
return CodeGeneratorResponse.newBuilder().setError("Usage: --flit_out=target=server,type=[spring|undertow]:<PATH>").build();
26+
return CodeGeneratorResponse.newBuilder().setError("Usage: --flit_out=target=server,type=[spring|undertow|jaxrs]:<PATH>").build();
2627
}
2728

2829
Map<String, Parameter> params = Parameter.of(request.getParameter());
@@ -50,6 +51,8 @@ private Generator resolveGenerator(Map<String, Parameter> params) {
5051
return new SpringGenerator();
5152
case "undertow":
5253
return new UndertowGenerator();
54+
case "jaxrs":
55+
return new JaxrsGenerator();
5356
default:
5457
throw new GeneratorException("Unknown server type: " + params.get(PARAM_TYPE).getValue());
5558
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.flit.protoc.gen.server.jaxrs;
2+
3+
import com.flit.protoc.gen.server.BaseGenerator;
4+
import com.flit.protoc.gen.server.BaseServerGenerator;
5+
import com.flit.protoc.gen.server.TypeMapper;
6+
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
7+
import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto;
8+
9+
public class JaxrsGenerator extends BaseServerGenerator {
10+
11+
@Override
12+
protected BaseGenerator getRpcGenerator(FileDescriptorProto proto, ServiceDescriptorProto service,
13+
String context, TypeMapper mapper) {
14+
return new RpcGenerator(proto, service, context, mapper);
15+
}
16+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.flit.protoc.gen.server.jaxrs;
2+
3+
import com.flit.protoc.gen.server.BaseGenerator;
4+
import com.flit.protoc.gen.server.TypeMapper;
5+
import com.google.protobuf.DescriptorProtos;
6+
import com.google.protobuf.DescriptorProtos.MethodDescriptorProto;
7+
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File;
8+
import com.squareup.javapoet.AnnotationSpec;
9+
import com.squareup.javapoet.ClassName;
10+
import com.squareup.javapoet.FieldSpec;
11+
import com.squareup.javapoet.MethodSpec;
12+
import com.squareup.javapoet.TypeSpec;
13+
import com.squareup.javapoet.TypeSpec.Builder;
14+
import java.util.Collections;
15+
import java.util.List;
16+
import javax.lang.model.element.Modifier;
17+
18+
public class RpcGenerator extends BaseGenerator {
19+
20+
public static final ClassName PATH = ClassName.bestGuess("javax.ws.rs.Path");
21+
public static final ClassName POST = ClassName.bestGuess("javax.ws.rs.POST");
22+
public static final ClassName PRODUCES = ClassName.bestGuess("javax.ws.rs.Produces");
23+
public static final ClassName CONSUMES = ClassName.bestGuess("javax.ws.rs.Consumes");
24+
private final String context;
25+
private final Builder rpcResource;
26+
27+
RpcGenerator(DescriptorProtos.FileDescriptorProto proto,
28+
DescriptorProtos.ServiceDescriptorProto service, String context, TypeMapper mapper) {
29+
super(proto, service, mapper);
30+
this.context = getContext(context);
31+
this.rpcResource = TypeSpec.classBuilder(getResourceName(service))
32+
.addModifiers(Modifier.PUBLIC)
33+
.addAnnotation(
34+
AnnotationSpec.builder(PATH).addMember("value", "$S",
35+
this.context + "/" + (proto.hasPackage() ? proto.getPackage() + "." : "") + service
36+
.getName()).build());
37+
addInstanceFields();
38+
addConstructor();
39+
service.getMethodList().forEach(this::addHandleMethod);
40+
}
41+
42+
private void addConstructor() {
43+
rpcResource.addMethod(MethodSpec.constructorBuilder()
44+
.addModifiers(Modifier.PUBLIC)
45+
.addParameter(getServiceInterface(), "service")
46+
.addStatement("this.service = service").build());
47+
}
48+
49+
private void addHandleMethod(MethodDescriptorProto mdp) {
50+
ClassName inputType = mapper.get(mdp.getInputType());
51+
ClassName outputType = mapper.get(mdp.getOutputType());
52+
rpcResource.addMethod(MethodSpec.methodBuilder("handle" + mdp.getName())
53+
.addModifiers(Modifier.PUBLIC)
54+
.addAnnotation(POST)
55+
.addAnnotation(AnnotationSpec.builder(PATH)
56+
.addMember("value", "$S", "/" + mdp.getName())
57+
.build())
58+
.addAnnotation(AnnotationSpec.builder(PRODUCES)
59+
.addMember("value", "$S", "application/protobuf")
60+
.addMember("value", "$S", "application/json")
61+
.build())
62+
.addAnnotation(AnnotationSpec.builder(CONSUMES)
63+
.addMember("value", "$S", "application/protobuf")
64+
.addMember("value", "$S", "application/json")
65+
.build())
66+
.addParameter(inputType, "request")
67+
.addStatement("return service.handle$L(request)", mdp.getName())
68+
.returns(outputType)
69+
.build());
70+
}
71+
72+
private ClassName getResourceName(DescriptorProtos.ServiceDescriptorProto service) {
73+
return ClassName.get(javaPackage, "Rpc" + service.getName() + "Resource");
74+
}
75+
76+
private void addInstanceFields() {
77+
rpcResource.addField(FieldSpec.builder(getServiceInterface(), "service")
78+
.addModifiers(Modifier.PRIVATE, Modifier.FINAL).build());
79+
}
80+
81+
@Override
82+
public List<File> getFiles() {
83+
return Collections.singletonList(toFile(getResourceName(service), rpcResource.build()));
84+
}
85+
}

plugin/src/test/java/com/flit/protoc/PluginTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class PluginTest {
1414
PluginProtos.CodeGeneratorResponse response = plugin.process();
1515

1616
assertTrue("Expected an error for no parameters", response.hasError());
17-
assertEquals("Incorrect error message", "Usage: --flit_out=target=server,type=[spring|undertow]:<PATH>", response.getError());
17+
assertEquals("Incorrect error message", "Usage: --flit_out=target=server,type=[spring|undertow|jaxrs]:<PATH>", response.getError());
1818
}
1919

2020
@Test public void test_NoTargetSpecified() {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.flit.protoc.gen.server.jaxrs;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNotNull;
5+
import static org.junit.Assert.assertTrue;
6+
7+
import com.flit.protoc.Plugin;
8+
import com.flit.protoc.gen.BaseGeneratorTest;
9+
import com.google.protobuf.compiler.PluginProtos;
10+
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File;
11+
import java.util.Map;
12+
import java.util.function.Function;
13+
import java.util.stream.Collectors;
14+
import org.junit.Test;
15+
16+
/**
17+
* Tests the generation of a service that has core definition imported from another file
18+
*/
19+
public class ContextGeneratorTest extends BaseGeneratorTest {
20+
21+
@Test
22+
public void test_GenerateWithMissingRoot() throws Exception {
23+
test_Route("context.missing.jaxrs.json", "/twirp/com.example.context.NullService");
24+
}
25+
26+
@Test
27+
public void test_GenerateWithEmptyRoot() throws Exception {
28+
test_Route("context.empty.jaxrs.json", "/twirp/com.example.context.NullService");
29+
}
30+
31+
@Test
32+
public void test_GenerateWithSlashOnlyRoot() throws Exception {
33+
test_Route("context.slash.jaxrs.json", "/com.example.context.NullService");
34+
}
35+
36+
@Test
37+
public void test_GenerateWithSlashRoot() throws Exception {
38+
test_Route("context.root.jaxrs.json", "/root/com.example.context.NullService");
39+
}
40+
41+
@Test
42+
public void test_GenerateWithNameRoot() throws Exception {
43+
test_Route("context.name.jaxrs.json", "/fibble/com.example.context.NullService");
44+
}
45+
46+
private void test_Route(String file, String route) throws Exception {
47+
PluginProtos.CodeGeneratorRequest request = loadJson(file);
48+
49+
Plugin plugin = new Plugin(request);
50+
PluginProtos.CodeGeneratorResponse response = plugin.process();
51+
52+
assertNotNull(response);
53+
assertEquals(2, response.getFileCount());
54+
55+
Map<String, File> files = response.getFileList()
56+
.stream()
57+
.collect(Collectors
58+
.toMap(PluginProtos.CodeGeneratorResponse.File::getName, Function.identity()));
59+
60+
assertTrue(files.containsKey("com/example/context/rpc/RpcNullService.java"));
61+
assertTrue(files.containsKey("com/example/context/rpc/RpcNullServiceResource.java"));
62+
63+
assertTrue(files.get("com/example/context/rpc/RpcNullServiceResource.java")
64+
.getContent()
65+
.contains(String.format("@Path(\"%s\")", route)));
66+
}
67+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.flit.protoc.gen.server.jaxrs;
2+
3+
import static java.util.stream.Collectors.toList;
4+
import static org.junit.Assert.assertEquals;
5+
import static org.junit.Assert.assertNotNull;
6+
7+
import com.flit.protoc.Plugin;
8+
import com.flit.protoc.gen.BaseGeneratorTest;
9+
import com.google.protobuf.compiler.PluginProtos;
10+
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File;
11+
import org.approvaltests.Approvals;
12+
import org.junit.Test;
13+
14+
public class HelloworldGeneratorTest extends BaseGeneratorTest {
15+
16+
@Test
17+
public void test_Generate() throws Exception {
18+
PluginProtos.CodeGeneratorRequest request = loadJson("helloworld.jaxrs.json");
19+
20+
Plugin plugin = new Plugin(request);
21+
PluginProtos.CodeGeneratorResponse response = plugin.process();
22+
23+
assertNotNull(response);
24+
assertEquals(2, response.getFileCount());
25+
System.out.println(response.getFile(0).getContent());
26+
assertEquals(response.getFile(0).getName(), "com/example/helloworld/RpcHelloWorld.java");
27+
assertEquals(response.getFile(1).getName(), "com/example/helloworld/RpcHelloWorldResource.java");
28+
29+
Approvals.verifyAll("", response.getFileList().stream().map(File::getContent).collect(toList()));
30+
response.getFileList().forEach(BaseGeneratorTest::assertParses);
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
[0] = package com.example.helloworld;
2+
3+
public interface RpcHelloWorld {
4+
Helloworld.HelloResp handleHello(Helloworld.HelloReq in);
5+
6+
Helloworld.HelloResp handleHelloAgain(Helloworld.HelloReq in);
7+
}
8+
9+
[1] = package com.example.helloworld;
10+
11+
import javax.ws.rs.Consumes;
12+
import javax.ws.rs.POST;
13+
import javax.ws.rs.Path;
14+
import javax.ws.rs.Produces;
15+
16+
@Path("/twirp/com.example.helloworld.HelloWorld")
17+
public class RpcHelloWorldResource {
18+
private final RpcHelloWorld service;
19+
20+
public RpcHelloWorldResource(RpcHelloWorld service) {
21+
this.service = service;
22+
}
23+
24+
@POST
25+
@Path("/Hello")
26+
@Produces({
27+
"application/protobuf",
28+
"application/json"
29+
})
30+
@Consumes({
31+
"application/protobuf",
32+
"application/json"
33+
})
34+
public Helloworld.HelloResp handleHello(Helloworld.HelloReq request) {
35+
return service.handleHello(request);
36+
}
37+
38+
@POST
39+
@Path("/HelloAgain")
40+
@Produces({
41+
"application/protobuf",
42+
"application/json"
43+
})
44+
@Consumes({
45+
"application/protobuf",
46+
"application/json"
47+
})
48+
public Helloworld.HelloResp handleHelloAgain(Helloworld.HelloReq request) {
49+
return service.handleHelloAgain(request);
50+
}
51+
}
52+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.flit.protoc.gen.server.jaxrs;
2+
3+
import static java.util.stream.Collectors.toList;
4+
import static org.junit.Assert.assertEquals;
5+
import static org.junit.Assert.assertNotNull;
6+
7+
import com.flit.protoc.Plugin;
8+
import com.flit.protoc.gen.BaseGeneratorTest;
9+
import com.google.protobuf.compiler.PluginProtos;
10+
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File;
11+
import org.approvaltests.Approvals;
12+
import org.junit.Test;
13+
14+
/**
15+
* Tests the generation of a service that has core definition imported from another file
16+
*/
17+
public class StatusGeneratorTest extends BaseGeneratorTest {
18+
19+
@Test public void test_Generate() throws Exception {
20+
PluginProtos.CodeGeneratorRequest request = loadJson("status.jaxrs.json");
21+
22+
Plugin plugin = new Plugin(request);
23+
PluginProtos.CodeGeneratorResponse response = plugin.process();
24+
25+
assertNotNull(response);
26+
assertEquals(2, response.getFileCount());
27+
28+
assertEquals(response.getFile(0).getName(), "com/example/helloworld/RpcStatus.java");
29+
assertEquals(response.getFile(1).getName(), "com/example/helloworld/RpcStatusResource.java");
30+
31+
Approvals.verifyAll("", response.getFileList().stream().map(File::getContent).collect(toList()));
32+
response.getFileList().forEach(BaseGeneratorTest::assertParses);
33+
}
34+
35+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[0] = package com.example.helloworld;
2+
3+
public interface RpcStatus {
4+
StatusOuterClass.StatusResponse handleGetStatus(Core.Empty in);
5+
}
6+
7+
[1] = package com.example.helloworld;
8+
9+
import javax.ws.rs.Consumes;
10+
import javax.ws.rs.POST;
11+
import javax.ws.rs.Path;
12+
import javax.ws.rs.Produces;
13+
14+
@Path("/twirp/com.example.helloworld.Status")
15+
public class RpcStatusResource {
16+
private final RpcStatus service;
17+
18+
public RpcStatusResource(RpcStatus service) {
19+
this.service = service;
20+
}
21+
22+
@POST
23+
@Path("/GetStatus")
24+
@Produces({
25+
"application/protobuf",
26+
"application/json"
27+
})
28+
@Consumes({
29+
"application/protobuf",
30+
"application/json"
31+
})
32+
public StatusOuterClass.StatusResponse handleGetStatus(Core.Empty request) {
33+
return service.handleGetStatus(request);
34+
}
35+
}
36+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"fileToGenerate": ["context.proto"],
3+
"parameter": "target=server,type=jaxrs,context=",
4+
"compilerVersion": {
5+
"major": 3,
6+
"minor": 5,
7+
"patch": 1,
8+
"suffix": ""
9+
},
10+
"protoFile": [{
11+
"name": "context.proto",
12+
"package": "com.example.context",
13+
"messageType": [{
14+
"name": "Empty"
15+
}],
16+
"service": [{
17+
"name": "NullService",
18+
"method": [{
19+
"name": "SayNull",
20+
"inputType": ".com.example.context.Empty",
21+
"outputType": ".com.example.context.Empty"
22+
}]
23+
}],
24+
"options": {
25+
"javaPackage": "com.example.context.rpc"
26+
},
27+
"syntax": "proto3"
28+
}]
29+
}

0 commit comments

Comments
 (0)