Skip to content

Commit

Permalink
feat(grpc): added grpc transcoding
Browse files Browse the repository at this point in the history
  • Loading branch information
zZHorizonZz committed Apr 24, 2024
1 parent 323774b commit 8e30e30
Show file tree
Hide file tree
Showing 42 changed files with 2,437 additions and 59 deletions.
4 changes: 4 additions & 0 deletions extensions/grpc-common/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
<dependency>
<groupId>com.google.api.grpc</groupId>
<artifactId>proto-google-common-protos</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-grpc</artifactId>
Expand Down
7 changes: 6 additions & 1 deletion extensions/grpc/api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,15 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.24.3</version>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>
</dependencies>

</project>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.quarkus.grpc;

import com.google.protobuf.Message;

public interface GrpcTranscoding {

String getGrpcServiceName();

<Req extends Message, Resp extends Message> GrpcTranscodingDescriptor<Req, Resp> findTranscodingDescriptor(
String methodName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.grpc;

public class GrpcTranscodingDescriptor<Req extends com.google.protobuf.Message, Resp extends com.google.protobuf.Message> {

private final GrpcTranscodingMarshaller<Req> requestMarshaller;
private final GrpcTranscodingMarshaller<Resp> responseMarshaller;

public GrpcTranscodingDescriptor(GrpcTranscodingMarshaller<Req> requestMarshaller,
GrpcTranscodingMarshaller<Resp> responseMarshaller) {
this.requestMarshaller = requestMarshaller;
this.responseMarshaller = responseMarshaller;
}

public GrpcTranscodingMarshaller<Req> getRequestMarshaller() {
return requestMarshaller;
}

public GrpcTranscodingMarshaller<Resp> getResponseMarshaller() {
return responseMarshaller;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package io.quarkus.grpc;

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

import org.jboss.logging.Logger;

import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;

import io.grpc.MethodDescriptor;
import io.grpc.Status;

public class GrpcTranscodingMarshaller<T extends Message> implements MethodDescriptor.PrototypeMarshaller<T> {

private final static Logger log = Logger.getLogger(GrpcTranscodingMarshaller.class);

private final T defaultInstance;

public GrpcTranscodingMarshaller(T defaultInstance) {
this.defaultInstance = checkNotNull(defaultInstance, "defaultInstance cannot be null");
}

@SuppressWarnings("unchecked")
@Override
public Class<T> getMessageClass() {
return (Class<T>) defaultInstance.getClass();
}

@Override
public T getMessagePrototype() {
return defaultInstance;
}

@Override
public InputStream stream(T value) {
try {
String response = JsonFormat.printer().omittingInsignificantWhitespace().print(value);
return new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8));
} catch (InvalidProtocolBufferException e) {
throw Status.INTERNAL.withDescription("Unable to convert message to JSON").withCause(e).asRuntimeException();
}
}

@SuppressWarnings("unchecked")
@Override
public T parse(InputStream stream) {
try (InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) {
Message.Builder builder = defaultInstance.newBuilderForType();
JsonFormat.parser().ignoringUnknownFields().merge(reader, builder);
return (T) builder.build();
} catch (InvalidProtocolBufferException e) {
log.error("Unable to parse JSON to message", e);
throw Status.INTERNAL.withDescription("Unable to parse JSON to message").withCause(e).asRuntimeException();
} catch (IOException e) {
log.error("An I/O error occurred while parsing the stream", e);
throw Status.INTERNAL.withDescription("An I/O error occurred while parsing the stream").withCause(e)
.asRuntimeException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.grpc;

import static java.lang.annotation.ElementType.METHOD;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GrpcTranscodingMethod {

String grpcMethodName();

String httpMethod();

String httpPath();
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public class GrpcCodeGen implements CodeGenProvider {
private static final String DESCRIPTOR_SET_OUTPUT_DIR = "quarkus.generate-code.grpc.descriptor-set.output-dir";
private static final String DESCRIPTOR_SET_FILENAME = "quarkus.generate-code.grpc.descriptor-set.name";

private static final String TRANSCODING_ENABLED = "quarkus.grpc.transcoding.enabled";

private static final String USE_ARG_FILE = "quarkus.generate-code.grpc.use-arg-file";

private Executables executables;
Expand Down Expand Up @@ -141,7 +143,7 @@ public boolean trigger(CodeGenContext context) throws CodeGenException {
}

if (!protoFiles.isEmpty()) {
initExecutables(workDir, context.applicationModel());
initExecutables(context, workDir, context.applicationModel());

Collection<String> protosToImport = gatherDirectoriesWithImports(workDir.resolve("protoc-dependencies"),
context);
Expand Down Expand Up @@ -241,7 +243,6 @@ private void postprocessing(CodeGenContext context, Path outDir) {
}

new GrpcPostProcessing(context, outDir).postprocess();

}

private Collection<Path> gatherProtosFromDependencies(Path workDir, Set<String> protoDirectories,
Expand Down Expand Up @@ -315,6 +316,7 @@ private Path getDescriptorSetOutputFile(CodeGenContext context) throws IOExcepti
}

private Collection<String> gatherDirectoriesWithImports(Path workDir, CodeGenContext context) throws CodeGenException {
log.info("Scanning dependencies for proto files to import");
Config properties = context.config();

String scanForImports = properties.getOptionalValue(SCAN_FOR_IMPORTS, String.class)
Expand All @@ -328,12 +330,16 @@ private Collection<String> gatherDirectoriesWithImports(Path workDir, CodeGenCon
List<String> dependenciesToScan = Arrays.stream(scanForImports.split(",")).map(String::trim)
.collect(Collectors.toList());

dependenciesToScan.add("com.google.api:api-common");
dependenciesToScan.add("com.google.api.grpc:proto-google-common-protos");

Set<String> importDirectories = new HashSet<>();
ApplicationModel appModel = context.applicationModel();
for (ResolvedDependency artifact : appModel.getRuntimeDependencies()) {
if (scanAll
|| dependenciesToScan.contains(
String.format("%s:%s", artifact.getGroupId(), artifact.getArtifactId()))) {
log.infof("Scanning %s:%s for proto files to import", artifact.getGroupId(), artifact.getArtifactId());
extractProtosFromArtifact(workDir, new ArrayList<>(), importDirectories, artifact, List.of(),
List.of(), false);
}
Expand Down Expand Up @@ -406,11 +412,12 @@ private String escapeWhitespace(String path) {
}
}

private void initExecutables(Path workDir, ApplicationModel model) throws CodeGenException {
private void initExecutables(CodeGenContext context, Path workDir, ApplicationModel model) throws CodeGenException {
if (executables == null) {
Path protocPath;
String protocPathProperty = System.getProperty("quarkus.grpc.protoc-path");
String classifier = System.getProperty("quarkus.grpc.protoc-os-classifier", osClassifier());

if (protocPathProperty == null) {
protocPath = findArtifactPath(model, PROTOC_GROUPID, PROTOC, classifier, EXE);
} else {
Expand All @@ -421,7 +428,7 @@ private void initExecutables(Path workDir, ApplicationModel model) throws CodeGe
Path protocGrpcPluginExe = prepareExecutable(workDir, model,
"io.grpc", "protoc-gen-grpc-java", classifier, "exe");

Path quarkusGrpcPluginExe = prepareQuarkusGrpcExecutable(model, workDir);
Path quarkusGrpcPluginExe = prepareQuarkusGrpcExecutable(context, model, workDir);

executables = new Executables(protocExe, protocGrpcPluginExe, quarkusGrpcPluginExe);
}
Expand Down Expand Up @@ -488,26 +495,28 @@ private String osClassifier() throws CodeGenException {
}
}

private static Path prepareQuarkusGrpcExecutable(ApplicationModel appModel, Path buildDir) throws CodeGenException {
private static Path prepareQuarkusGrpcExecutable(CodeGenContext context, ApplicationModel appModel, Path buildDir)
throws CodeGenException {
Path pluginPath = findArtifactPath(appModel, "io.quarkus", "quarkus-grpc-protoc-plugin", "shaded", "jar");
if (pluginPath == null) {
throw new CodeGenException("Failed to find Quarkus gRPC protoc plugin among dependencies");
}

if (OS.determineOS() != OS.WINDOWS) {
return writeScript(buildDir, pluginPath, "#!/bin/sh\n", ".sh");
return writeScript(context, buildDir, pluginPath, "#!/bin/sh\n", ".sh");
} else {
return writeScript(buildDir, pluginPath, "@echo off\r\n", ".cmd");
return writeScript(context, buildDir, pluginPath, "@echo off\r\n", ".cmd");
}
}

private static Path writeScript(Path buildDir, Path pluginPath, String shebang, String suffix) throws CodeGenException {
private static Path writeScript(CodeGenContext context, Path buildDir, Path pluginPath, String shebang, String suffix)
throws CodeGenException {
Path script;
try {
script = Files.createTempFile(buildDir, "quarkus-grpc", suffix);
try (BufferedWriter writer = Files.newBufferedWriter(script)) {
writer.write(shebang);
writePluginExeCmd(pluginPath, writer);
writePluginExeCmd(context, pluginPath, writer);
}
} catch (IOException e) {
throw new CodeGenException("Failed to create a wrapper script for quarkus-grpc plugin", e);
Expand All @@ -518,9 +527,13 @@ private static Path writeScript(Path buildDir, Path pluginPath, String shebang,
return script;
}

private static void writePluginExeCmd(Path pluginPath, BufferedWriter writer) throws IOException {
private static void writePluginExeCmd(CodeGenContext context, Path pluginPath, BufferedWriter writer) throws IOException {
Config properties = context.config();
boolean enableTranscoding = properties.getOptionalValue(TRANSCODING_ENABLED, Boolean.class).orElse(false);

writer.write("\"" + JavaBinFinder.findBin() + "\" -cp \"" +
pluginPath.toAbsolutePath() + "\" " + quarkusProtocPluginMain);
pluginPath.toAbsolutePath() + "\" " + quarkusProtocPluginMain
+ (enableTranscoding ? " --enableTranscoding" : ""));
writer.newLine();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
import org.jboss.jandex.DotName;

import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.grpc.deployment.GrpcClientBuildItem.ClientInfo;
import io.quarkus.grpc.deployment.GrpcClientBuildItem.ClientType;

public final class GrpcClientBuildItem extends MultiBuildItem {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import io.quarkus.grpc.GlobalInterceptor;
import io.quarkus.grpc.GrpcClient;
import io.quarkus.grpc.GrpcService;
import io.quarkus.grpc.GrpcTranscoding;
import io.quarkus.grpc.GrpcTranscodingMethod;
import io.quarkus.grpc.MutinyBean;
import io.quarkus.grpc.MutinyClient;
import io.quarkus.grpc.MutinyGrpc;
Expand Down Expand Up @@ -49,6 +51,9 @@ public class GrpcDotNames {
public static final DotName MUTINY_BEAN = DotName.createSimple(MutinyBean.class.getName());
public static final DotName MUTINY_SERVICE = DotName.createSimple(MutinyService.class.getName());

public static final DotName GRPC_TRANSCODING = DotName.createSimple(GrpcTranscoding.class.getName());
public static final DotName GRPC_METHOD = DotName.createSimple(GrpcTranscodingMethod.class.getName());

public static final DotName GLOBAL_INTERCEPTOR = DotName.createSimple(GlobalInterceptor.class.getName());
public static final DotName REGISTER_INTERCEPTOR = DotName.createSimple(RegisterInterceptor.class.getName());
public static final DotName REGISTER_INTERCEPTORS = DotName.createSimple(RegisterInterceptors.class.getName());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.grpc.deployment;

import java.util.List;

import org.jboss.jandex.DotName;

import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.grpc.transcoding.GrpcTranscodingMethod;

public final class GrpcTranscodingBuildItem extends MultiBuildItem {

final DotName marshallingClass;
final List<GrpcTranscodingMethod> transcodingMethods;

public GrpcTranscodingBuildItem(DotName marshallingClass, List<GrpcTranscodingMethod> transcodingMethods) {
this.marshallingClass = marshallingClass;
this.transcodingMethods = transcodingMethods;
}

public DotName getMarshallingClass() {
return marshallingClass;
}

public List<GrpcTranscodingMethod> getTranscodingMethods() {
return transcodingMethods;
}
}
Loading

0 comments on commit 8e30e30

Please sign in to comment.