Skip to content

Commit 440bfdd

Browse files
authored
add jakarta implementation of plugin and runtime jars (#11)
1 parent ccd9b6b commit 440bfdd

27 files changed

Lines changed: 1028 additions & 31 deletions

README.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ It supports the generation of Java based servers with the following flavours sup
88
+ [Spring Boot/Spring MVC](https://spring.io/projects/spring-boot "Spring Boot")
99
+ [Undertow](http://undertow.io/ "Undertow")
1010
+ JAX-RS ([Jersey](https://eclipse-ee4j.github.io/jersey/), [Apache CFX](http://cxf.apache.org/))
11+
+ [Jakarta EE](https://jakarta.ee/ "Jakarta")
1112

1213
## Building & Running
1314

@@ -24,13 +25,14 @@ To test you will need an installation of the [protocol buffers compiler](https:/
2425

2526
The project is split into the following modules:
2627

27-
| Module | Description |
28-
|:------------------|:------------------------------------------------------|
29-
| `plugin` | The `protoc` plugin |
30-
| `runtime:core` | Core functionality required by generated code |
31-
| `runtime:jaxrs` | Runtime library for JAX-RS servers |
32-
| `runtime:spring` | Runtime library for Spring MVC/Boot servers |
33-
| `runtime:undertow`| Runtime library for Undertow servers |
28+
| Module | Description |
29+
|:-------------------|:----------------------------------------------|
30+
| `plugin` | The `protoc` plugin |
31+
| `runtime:core` | Core functionality required by generated code |
32+
| `runtime:jakarta` | Runtime library for Jakarta servers |
33+
| `runtime:jaxrs` | Runtime library for JAX-RS servers |
34+
| `runtime:spring` | Runtime library for Spring MVC/Boot servers |
35+
| `runtime:undertow` | Runtime library for Undertow servers |
3436

3537

3638
### Build
@@ -63,12 +65,12 @@ The plugin is executed as part of a protoc compilation step:
6365

6466
The flit plugin accepts the following plugin parameters:
6567

66-
| Name | Required | Type | Description |
67-
|:----------|:---------:|:----------------------------------|:-------------------------------------------------------|
68-
| `target` | Y | `enum[server]` | The type of target to generate e.g. server, client etc |
69-
| `type` | Y | `enum[spring,undertow,boot,jaxrs]`| Type of target to generate |
70-
| `context` | N | `string` | Base context for routing, default is `/twirp` |
71-
| `request` | N | `string` | If the request parameter should pass to the service |
68+
| Name | Required | Type | Description |
69+
|:----------|:---------:|:-------------------------------------------|:-------------------------------------------------------|
70+
| `target` | Y | `enum[server]` | The type of target to generate e.g. server, client etc |
71+
| `type` | Y | `enum[spring,undertow,boot,jakarta,jaxrs]` | Type of target to generate |
72+
| `context` | N | `string` | Base context for routing, default is `/twirp` |
73+
| `request` | N | `string` | If the request parameter should pass to the service |
7274

7375
# Development
7476

build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ allprojects {
1414

1515
ext {
1616
// third party
17+
jakartaservletapiVersion = "5.0.0"
18+
jakartawsrsapiVersion = "3.0.0"
1719
javapoetVersion = "1.13.0"
1820
javaxservletapiVersion = "4.0.1"
1921
javaxwsrsapiVersion = "2.1.1"
@@ -25,7 +27,8 @@ allprojects {
2527
// testing
2628
approvaltestsVersion = "18.5.0"
2729
javaparserVersion = "3.25.2"
28-
jerseyCommonVersion = "2.22.2"
30+
jerseyCommonJavaxVersion = "2.22.2"
31+
jerseyCommonJakartaVersion = "3.1.1"
2932
junitJupiterVersion = "5.9.2"
3033
mockitoVersion = "5.2.0"
3134
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import com.flit.protoc.gen.Generator;
88
import com.flit.protoc.gen.GeneratorException;
9+
import com.flit.protoc.gen.server.jakarta.JakartaGenerator;
910
import com.flit.protoc.gen.server.jaxrs.JaxrsGenerator;
1011
import com.flit.protoc.gen.server.spring.SpringGenerator;
1112
import com.flit.protoc.gen.server.undertow.UndertowGenerator;
@@ -59,6 +60,8 @@ private Generator resolveGenerator(Map<String, Parameter> params) {
5960
return new UndertowGenerator(requestServices);
6061
case "jaxrs":
6162
return new JaxrsGenerator(requestServices);
63+
case "jakarta":
64+
return new JakartaGenerator(requestServices);
6265
default:
6366
throw new GeneratorException("Unknown server type: " + params.get(PARAM_TYPE).getValue());
6467
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.flit.protoc.gen.server.jakarta;
2+
3+
import static com.flit.protoc.gen.server.jakarta.RpcGenerator.HttpServletRequest;
4+
5+
import com.flit.protoc.gen.server.BaseGenerator;
6+
import com.flit.protoc.gen.server.BaseServerGenerator;
7+
import com.flit.protoc.gen.server.TypeMapper;
8+
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
9+
import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto;
10+
import com.squareup.javapoet.TypeName;
11+
import java.util.List;
12+
13+
public class JakartaGenerator extends BaseServerGenerator {
14+
15+
public JakartaGenerator(List<String> requestServices) {
16+
super(requestServices);
17+
}
18+
19+
@Override
20+
protected BaseGenerator getRpcGenerator(FileDescriptorProto proto, ServiceDescriptorProto service,
21+
String context, TypeMapper mapper) {
22+
return new RpcGenerator(proto, service, context, mapper, isRequestBasedClass(service));
23+
}
24+
25+
@Override
26+
protected TypeName getHttpRequestTypeName() {
27+
return HttpServletRequest;
28+
}
29+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package com.flit.protoc.gen.server.jakarta;
2+
3+
import com.flit.protoc.gen.server.BaseGenerator;
4+
import com.flit.protoc.gen.server.TypeMapper;
5+
import com.flit.protoc.gen.server.Types;
6+
import com.google.common.net.MediaType;
7+
import com.google.protobuf.DescriptorProtos;
8+
import com.google.protobuf.DescriptorProtos.MethodDescriptorProto;
9+
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File;
10+
import com.squareup.javapoet.AnnotationSpec;
11+
import com.squareup.javapoet.ClassName;
12+
import com.squareup.javapoet.FieldSpec;
13+
import com.squareup.javapoet.MethodSpec;
14+
import com.squareup.javapoet.ParameterSpec;
15+
import com.squareup.javapoet.TypeSpec;
16+
import com.squareup.javapoet.TypeSpec.Builder;
17+
import java.util.Collections;
18+
import java.util.List;
19+
import javax.lang.model.element.Modifier;
20+
21+
public class RpcGenerator extends BaseGenerator {
22+
23+
static final ClassName PATH = ClassName.bestGuess("jakarta.ws.rs.Path");
24+
static final ClassName POST = ClassName.bestGuess("jakarta.ws.rs.POST");
25+
static final ClassName CONTEXT = ClassName.bestGuess("jakarta.ws.rs.core.Context");
26+
static final ClassName HttpServletRequest = ClassName.bestGuess("jakarta.servlet.http.HttpServletRequest");
27+
static final ClassName HttpServletResponse = ClassName.bestGuess("jakarta.servlet.http.HttpServletResponse");
28+
29+
private final Builder rpcResource;
30+
private final boolean passRequest;
31+
32+
RpcGenerator(
33+
DescriptorProtos.FileDescriptorProto proto,
34+
DescriptorProtos.ServiceDescriptorProto service,
35+
String context,
36+
TypeMapper mapper,
37+
boolean passRequest
38+
) {
39+
super(proto, service, mapper);
40+
String prefix = getContext(context);
41+
this.passRequest = passRequest;
42+
this.rpcResource = TypeSpec.classBuilder(getResourceName(service))
43+
.addModifiers(Modifier.PUBLIC)
44+
.addAnnotation(
45+
AnnotationSpec.builder(PATH).addMember("value", "$S",
46+
prefix + "/" + (proto.hasPackage() ? proto.getPackage() + "." : "") + service
47+
.getName()).build());
48+
addInstanceFields();
49+
addConstructor();
50+
service.getMethodList().forEach(this::addHandleMethod);
51+
}
52+
53+
private void addConstructor() {
54+
rpcResource.addMethod(MethodSpec.constructorBuilder()
55+
.addModifiers(Modifier.PUBLIC)
56+
.addParameter(getServiceInterface(), "service")
57+
.addStatement("this.service = service").build());
58+
}
59+
60+
private void addHandleMethod(MethodDescriptorProto mdp) {
61+
ClassName inputType = mapper.get(mdp.getInputType());
62+
ClassName outputType = mapper.get(mdp.getOutputType());
63+
rpcResource.addMethod(MethodSpec.methodBuilder("handle" + mdp.getName())
64+
.addModifiers(Modifier.PUBLIC)
65+
.addAnnotation(POST)
66+
.addAnnotation(AnnotationSpec.builder(PATH)
67+
.addMember("value", "$S", "/" + mdp.getName())
68+
.build())
69+
.addParameter(ParameterSpec.builder(HttpServletRequest, "request")
70+
.addAnnotation(CONTEXT).build())
71+
.addParameter(ParameterSpec.builder(HttpServletResponse, "response")
72+
.addAnnotation(CONTEXT).build())
73+
.addException(Types.Exception)
74+
.addStatement("boolean json = false")
75+
.addStatement("final $T data", inputType)
76+
.beginControlFlow("if (request.getContentType().equals($S))", MediaType.PROTOBUF.toString())
77+
.addStatement("data = $T.parseFrom(request.getInputStream())", inputType)
78+
.nextControlFlow("else if (request.getContentType().startsWith($S))", "application/json")
79+
.addStatement("json = true")
80+
.addStatement("$T.Builder builder = $T.newBuilder()", inputType, inputType)
81+
.addStatement("$T.parser().merge(new $T(request.getInputStream(), $T.UTF_8), builder)",
82+
Types.JsonFormat,
83+
Types.InputStreamReader,
84+
Types.StandardCharsets)
85+
.addStatement("data = builder.build()")
86+
.nextControlFlow("else")
87+
.addStatement("response.setStatus(415)")
88+
.addStatement("response.flushBuffer()")
89+
.addStatement("return")
90+
.endControlFlow()
91+
// route to the service
92+
.addStatement(getRouteToService(), outputType, mdp.getName())
93+
.addStatement("response.setStatus(200)")
94+
// send the response
95+
.beginControlFlow("if (json)")
96+
.addStatement("response.setContentType($S)", MediaType.JSON_UTF_8.toString())
97+
.addStatement("response.getOutputStream().write($T.printer().omittingInsignificantWhitespace().print(retval).getBytes($T.UTF_8))",
98+
Types.JsonFormat,
99+
Types.StandardCharsets)
100+
.nextControlFlow("else")
101+
.addStatement("response.setContentType($S)", MediaType.PROTOBUF.toString())
102+
.addStatement("retval.writeTo(response.getOutputStream())")
103+
.endControlFlow()
104+
.addStatement("response.flushBuffer()")
105+
.build());
106+
}
107+
108+
private String getRouteToService() {
109+
if (passRequest) {
110+
return "$T retval = service.handle$L(request, data)";
111+
} else {
112+
return "$T retval = service.handle$L(data)";
113+
}
114+
}
115+
116+
private ClassName getResourceName(DescriptorProtos.ServiceDescriptorProto service) {
117+
return ClassName.get(javaPackage, "Rpc" + service.getName() + "Resource");
118+
}
119+
120+
private void addInstanceFields() {
121+
rpcResource.addField(FieldSpec.builder(getServiceInterface(), "service")
122+
.addModifiers(Modifier.PRIVATE, Modifier.FINAL).build());
123+
}
124+
125+
@Override
126+
public List<File> getFiles() {
127+
return Collections.singletonList(toFile(getResourceName(service), rpcResource.build()));
128+
}
129+
}

plugin/src/main/java/com/flit/protoc/gen/server/jaxrs/RpcGenerator.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,12 @@
2020

2121
public class RpcGenerator extends BaseGenerator {
2222

23-
public static final ClassName PATH = ClassName.bestGuess("javax.ws.rs.Path");
24-
public static final ClassName POST = ClassName.bestGuess("javax.ws.rs.POST");
25-
public static final ClassName PRODUCES = ClassName.bestGuess("javax.ws.rs.Produces");
26-
public static final ClassName CONSUMES = ClassName.bestGuess("javax.ws.rs.Consumes");
27-
public static final ClassName CONTEXT = ClassName.bestGuess("javax.ws.rs.core.Context");
28-
public static final ClassName HttpServletRequest = ClassName.bestGuess("javax.servlet.http.HttpServletRequest");
29-
public static final ClassName HttpServletResponse = ClassName.bestGuess("javax.servlet.http.HttpServletResponse");
30-
private final String context;
23+
static final ClassName PATH = ClassName.bestGuess("javax.ws.rs.Path");
24+
static final ClassName POST = ClassName.bestGuess("javax.ws.rs.POST");
25+
static final ClassName CONTEXT = ClassName.bestGuess("javax.ws.rs.core.Context");
26+
static final ClassName HttpServletRequest = ClassName.bestGuess("javax.servlet.http.HttpServletRequest");
27+
static final ClassName HttpServletResponse = ClassName.bestGuess("javax.servlet.http.HttpServletResponse");
28+
3129
private final Builder rpcResource;
3230
private final boolean passRequest;
3331

@@ -39,13 +37,13 @@ public class RpcGenerator extends BaseGenerator {
3937
boolean passRequest
4038
) {
4139
super(proto, service, mapper);
42-
this.context = getContext(context);
40+
String prefix = getContext(context);
4341
this.passRequest = passRequest;
4442
this.rpcResource = TypeSpec.classBuilder(getResourceName(service))
4543
.addModifiers(Modifier.PUBLIC)
4644
.addAnnotation(
4745
AnnotationSpec.builder(PATH).addMember("value", "$S",
48-
this.context + "/" + (proto.hasPackage() ? proto.getPackage() + "." : "") + service
46+
prefix + "/" + (proto.hasPackage() ? proto.getPackage() + "." : "") + service
4947
.getName()).build());
5048
addInstanceFields();
5149
addConstructor();

plugin/src/main/java/com/flit/protoc/gen/server/spring/RpcGenerator.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414

1515
class RpcGenerator extends BaseGenerator {
1616

17-
public static final ClassName RestController = ClassName.bestGuess("org.springframework.web.bind.annotation.RestController");
18-
public static final ClassName Autowired = ClassName.bestGuess("org.springframework.beans.factory.annotation.Autowired");
19-
public static final ClassName PostMapping = ClassName.bestGuess("org.springframework.web.bind.annotation.PostMapping");
20-
public static final ClassName HttpServletRequest = ClassName.bestGuess("javax.servlet.http.HttpServletRequest");
21-
public static final ClassName HttpServletResponse = ClassName.bestGuess("javax.servlet.http.HttpServletResponse");
17+
static final ClassName RestController = ClassName.bestGuess("org.springframework.web.bind.annotation.RestController");
18+
static final ClassName Autowired = ClassName.bestGuess("org.springframework.beans.factory.annotation.Autowired");
19+
static final ClassName PostMapping = ClassName.bestGuess("org.springframework.web.bind.annotation.PostMapping");
20+
static final ClassName HttpServletRequest = ClassName.bestGuess("javax.servlet.http.HttpServletRequest");
21+
static final ClassName HttpServletResponse = ClassName.bestGuess("javax.servlet.http.HttpServletResponse");
2222

2323
private final String context;
2424
private final TypeSpec.Builder rpcController;
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.jakarta;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
import static org.junit.jupiter.api.Assertions.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.jupiter.api.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.jakarta.json", "/twirp/com.example.context.NullService");
24+
}
25+
26+
@Test
27+
public void test_GenerateWithEmptyRoot() throws Exception {
28+
test_Route("context.empty.jakarta.json", "/twirp/com.example.context.NullService");
29+
}
30+
31+
@Test
32+
public void test_GenerateWithSlashOnlyRoot() throws Exception {
33+
test_Route("context.slash.jakarta.json", "/com.example.context.NullService");
34+
}
35+
36+
@Test
37+
public void test_GenerateWithSlashRoot() throws Exception {
38+
test_Route("context.root.jakarta.json", "/root/com.example.context.NullService");
39+
}
40+
41+
@Test
42+
public void test_GenerateWithNameRoot() throws Exception {
43+
test_Route("context.name.jakarta.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(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+
}

0 commit comments

Comments
 (0)