Skip to content

Commit

Permalink
More idiomatic play integration #420 (#534)
Browse files Browse the repository at this point in the history
  • Loading branch information
ctoomey authored and raboof committed Mar 21, 2019
1 parent ae4936c commit 114b2f3
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 19 deletions.
10 changes: 6 additions & 4 deletions codegen/src/main/scala/akka/grpc/gen/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ object Main extends App {

private val serverPowerApis: Boolean = reqLowerCase.contains("server_power_apis=true")

private val usePlayActions: Boolean = reqLowerCase.contains("use_play_actions=true")

val LogFileRegex = """(?:.*,)logfile=([^,]+)(?:,.*)?""".r
private val logger = req.getParameter match {
case LogFileRegex(path) => new FileLogger(path)
Expand Down Expand Up @@ -71,18 +73,18 @@ object Main extends App {
if (languageScala) {
// Scala
val base =
if (generateClient && generateServer) Seq(ScalaTraitCodeGenerator, PlayScalaClientCodeGenerator, PlayScalaServerCodeGenerator(serverPowerApis))
if (generateClient && generateServer) Seq(ScalaTraitCodeGenerator, PlayScalaClientCodeGenerator, PlayScalaServerCodeGenerator(serverPowerApis, usePlayActions))
else if (generateClient) Seq(ScalaTraitCodeGenerator, PlayScalaClientCodeGenerator)
else if (generateServer) Seq(ScalaTraitCodeGenerator, PlayScalaServerCodeGenerator(serverPowerApis))
else if (generateServer) Seq(ScalaTraitCodeGenerator, PlayScalaServerCodeGenerator(serverPowerApis, usePlayActions))
else throw new IllegalArgumentException("At least one of generateClient or generateServer must be enabled")
if (serverPowerApis) Seq(ScalaPowerApiTraitCodeGenerator) ++ base
else base
} else {
// Java
val base =
if (generateClient && generateServer) Seq(JavaInterfaceCodeGenerator, JavaClientCodeGenerator, PlayJavaClientCodeGenerator, PlayJavaServerCodeGenerator(serverPowerApis))
if (generateClient && generateServer) Seq(JavaInterfaceCodeGenerator, JavaClientCodeGenerator, PlayJavaClientCodeGenerator, PlayJavaServerCodeGenerator(serverPowerApis, usePlayActions))
else if (generateClient) Seq(JavaInterfaceCodeGenerator, JavaClientCodeGenerator, PlayJavaClientCodeGenerator)
else if (generateServer) Seq(JavaInterfaceCodeGenerator, PlayJavaServerCodeGenerator(serverPowerApis))
else if (generateServer) Seq(JavaInterfaceCodeGenerator, PlayJavaServerCodeGenerator(serverPowerApis, usePlayActions))
else throw new IllegalArgumentException("At least one of generateClient or generateServer must be enabled")
if (serverPowerApis) Seq(JavaPowerApiInterfaceCodeGenerator) ++ base
else base
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import akka.grpc.gen.Logger
import akka.grpc.gen.javadsl.{ JavaCodeGenerator, Service }
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse
import templates.PlayJavaServer.txt.Router
import templates.PlayJavaServer.txt.RouterUsingActions

object PlayJavaServerCodeGenerator extends PlayJavaServerCodeGenerator(powerApis = false)

case class PlayJavaServerCodeGenerator(powerApis: Boolean = false) extends JavaCodeGenerator {
case class PlayJavaServerCodeGenerator(powerApis: Boolean = false, usePlayActions: Boolean = false) extends JavaCodeGenerator {
override def name: String = "akka-grpc-play-server-java"

override def perServiceContent = super.perServiceContent + generateRouter() ++ (
if (powerApis) Set(generateRouter(powerApis))
else Set.empty
override def perServiceContent = super.perServiceContent ++ (
if (powerApis && usePlayActions) Set(generateRouterUsingActions(), generateRouterUsingActions(powerApis))
else if (powerApis) Set(generateRouter(), generateRouter(powerApis))
else if (usePlayActions) Set(generateRouterUsingActions())
else Set(generateRouter())
)

private def generateRouter(powerApis: Boolean = false): (Logger, Service) => CodeGeneratorResponse.File = (logger, service) => {
Expand All @@ -26,4 +27,12 @@ case class PlayJavaServerCodeGenerator(powerApis: Boolean = false) extends JavaC
logger.info(s"Generating Akka gRPC service${if (powerApis) " power API" else ""} play router for ${service.packageName}.${service.name}")
b.build
}

private def generateRouterUsingActions(powerApis: Boolean = false): (Logger, Service) => CodeGeneratorResponse.File = (logger, service) => {
val b = CodeGeneratorResponse.File.newBuilder()
b.setContent(RouterUsingActions(service, powerApis).body)
b.setName(s"${service.packageDir}/Abstract${service.name}${if (powerApis) "PowerApi" else ""}Router.java")
logger.info(s"Generating Akka gRPC service${if (powerApis) " power API" else ""} play router for ${service.packageName}.${service.name}")
b.build
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ import akka.grpc.gen.scaladsl.{ ScalaCodeGenerator, ScalaServerCodeGenerator, Se
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse
import templates.PlayScala.txt._

object PlayScalaServerCodeGenerator extends PlayScalaServerCodeGenerator(powerApis = false)
case class PlayScalaServerCodeGenerator(powerApis: Boolean = false, usePlayActions: Boolean = false) extends ScalaCodeGenerator {

case class PlayScalaServerCodeGenerator(powerApis: Boolean = false) extends ScalaCodeGenerator {
override def name: String = "akka-grpc-play-server-scala"

override def perServiceContent = super.perServiceContent + generateRouter() ++ (
if (powerApis) Set(generateRouter(powerApis))
else Set.empty
)
override def perServiceContent = super.perServiceContent ++ ((powerApis, usePlayActions) match {
case (true, true) => Set(generateRouterUsingActions(), generateRouterUsingActions(true))
case (false, true) => Set(generateRouterUsingActions())
case (true, false) => Set(generateRouter(), generateRouter(true))
case (false, false) => Set(generateRouter())
})

private def generateRouter(powerApis: Boolean = false): (Logger, Service) => CodeGeneratorResponse.File = (logger, service) => {
val b = CodeGeneratorResponse.File.newBuilder()
Expand All @@ -26,4 +27,12 @@ case class PlayScalaServerCodeGenerator(powerApis: Boolean = false) extends Scal
logger.info(s"Generating Akka gRPC service${if (powerApis) " power API" else ""} play router for ${service.packageName}.${service.name}")
b.build
}

private def generateRouterUsingActions(powerApis: Boolean = false): (Logger, Service) => CodeGeneratorResponse.File = (logger, service) => {
val b = CodeGeneratorResponse.File.newBuilder()
b.setContent(RouterUsingActions(service, powerApis).body)
b.setName(s"${service.packageDir}/Abstract${service.name}${if (powerApis) "PowerApi" else ""}Router.scala")
logger.info(s"Generating Akka gRPC service${if (powerApis) " power API" else ""} play router for ${service.packageName}.${service.name}")
b.build
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
@*
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
*@

@(service: akka.grpc.gen.javadsl.Service, powerApis: Boolean)

@akka.grpc.gen.Constants.DoNotEditComment
package @service.packageName;

import akka.annotation.InternalApi;
import akka.actor.ActorSystem;
import akka.grpc.GrpcServiceException;
import akka.grpc.internal.PlayRouterHelper$;
import akka.grpc.internal.PlayRouterUsingActions;
import akka.grpc.javadsl.GrpcExceptionHandler;
import akka.http.javadsl.model.HttpRequest;
import akka.http.javadsl.model.HttpResponse;
import akka.japi.Function;
import akka.stream.Materializer;
import play.api.mvc.EssentialAction;
import play.api.mvc.RequestHeader;
import play.api.mvc.PlayBodyParsers;
import play.api.mvc.ActionBuilder;
import play.api.mvc.Request;
import play.api.mvc.AnyContent;

import scala.Function1;
import scala.NotImplementedError;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletionStage;

@defining(if (powerApis) service.name + "PowerApi" else service.name) { serviceName =>
/**
* Abstract base class for implementing @{serviceName} and using as a play Router
*/
public abstract class Abstract@{serviceName}Router extends PlayRouterUsingActions implements @{serviceName} {
private final Function<ActorSystem, Function<Throwable, io.grpc.Status>> eHandler;
private final ActorSystem system;
private final PlayBodyParsers parsers;
private final ActionBuilder<Request, AnyContent> actionBuilder;
@{
val (streamingInputMethods: Seq[String], unaryInputMethods: Seq[String]) = service.methods.partition(_.inputStreaming) match {
case (streaming, unary) => (streaming.map(_.grpcName), unary.map(_.grpcName))
}
s"""
private final List<String> streamingInputMethods = Arrays.asList(${streamingInputMethods.mkString("\"", "\", \"", "\"")});
private final List<String> unaryInputMethods = Arrays.asList(${unaryInputMethods.mkString("\"", "\", \"", "\"")});
"""
}

public Abstract@{serviceName}Router(Materializer mat, ActorSystem system, PlayBodyParsers parsers, ActionBuilder<Request, AnyContent> actionBuilder) {
this(mat, system, parsers, actionBuilder, GrpcExceptionHandler.defaultMapper());
}

public Abstract@{serviceName}Router(Materializer mat, ActorSystem system, PlayBodyParsers parsers, ActionBuilder<Request, AnyContent> actionBuilder, Function<ActorSystem, Function<Throwable, io.grpc.Status>> eHandler) {
super(mat, @{service.name}.name, parsers, actionBuilder);
this.eHandler = eHandler;
this.system = system;
this.parsers = parsers;
this.actionBuilder = actionBuilder;
}

/**
* INTERNAL API
*/
@@InternalApi
@@Override
final public Function1<RequestHeader, EssentialAction> createHandler(String serviceName, Materializer mat) {
return new Function1<RequestHeader, EssentialAction>() {
Function<HttpRequest, CompletionStage<HttpResponse>> handler = @{serviceName}HandlerFactory.create(Abstract@{serviceName}Router.this, serviceName, mat, eHandler, system);
public EssentialAction apply(RequestHeader reqOuter) {
String[] pathSegments = reqOuter.path().split("/");
if ((pathSegments.length == 3) && (pathSegments[1].equals(serviceName))) {
String method = pathSegments[2];
if (streamingInputMethods.contains(method)) return createStreamingAction(PlayRouterHelper$.MODULE$.handlerFor(handler), mat.executionContext());
else if (unaryInputMethods.contains(method)) return createUnaryAction(PlayRouterHelper$.MODULE$.handlerFor(handler), mat.executionContext());
else throw new NotImplementedError("Not implemented: " + method);
} else {
throw new GrpcServiceException(io.grpc.Status.INTERNAL.withDescription("Unexpected/handled path " + reqOuter.path()));
}
}
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
@*
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
*@

@(service: akka.grpc.gen.scaladsl.Service, powerApis: Boolean)

@akka.grpc.gen.Constants.DoNotEditComment
package @service.packageName

import akka.annotation.InternalApi
import akka.actor.ActorSystem
import akka.grpc.GrpcServiceException
import akka.grpc.internal.PlayRouterUsingActions
import akka.grpc.scaladsl.GrpcExceptionHandler.defaultMapper
import akka.http.scaladsl.model.Uri.Path
import akka.http.scaladsl.model.Uri.Path.Segment
import akka.stream.Materializer
import io.grpc.Status
import play.api.mvc.{EssentialAction, RequestHeader, PlayBodyParsers, ActionBuilder, Request, AnyContent}

import scala.concurrent.ExecutionContext

@defining(if (powerApis) service.name + "PowerApi" else service.name) { serviceName =>
/**
* Abstract base class for implementing @{serviceName} and using as a play Router
*/
abstract class Abstract@{serviceName}Router(mat: Materializer, system: ActorSystem, parsers: PlayBodyParsers, actionBuilder: ActionBuilder[Request, AnyContent], eHandler: ActorSystem => PartialFunction[Throwable, Status] = defaultMapper) extends PlayRouterUsingActions(mat, @{service.name}.name, parsers, actionBuilder) with @{serviceName} {

@{
val (streamingInputMethods: Seq[String], unaryInputMethods: Seq[String]) = service.methods.partition(_.inputStreaming) match {
case (streaming, unary) => (streaming.map(_.grpcName), unary.map(_.grpcName))
}
"val (streamingInputMethods: Seq[String], unaryInputMethods: Seq[String]) = (Seq(" + streamingInputMethods.mkString("\"", "\", \"", "\"") + "), Seq(" + unaryInputMethods.mkString("\"", "\", \"", "\"") + "))"
}

/**
* INTERNAL API
*/
@@InternalApi
final override protected def createHandler(serviceName: String, mat: Materializer): RequestHeader => EssentialAction = {
val handler = @{serviceName}Handler(this, serviceName, eHandler)(mat, system)
reqOuter =>
implicit val ec: ExecutionContext = mat.executionContext
Path(reqOuter.path) match {
case Path.Slash(Segment(`serviceName`, Path.Slash(Segment(method, Path.Empty)))) =>
if (streamingInputMethods.contains(method)) createStreamingAction(handler)
else if (unaryInputMethods.contains(method)) createUnaryAction(handler)
else throw new NotImplementedError(s"Not implemented: $method")
case _ => throw new GrpcServiceException(io.grpc.Status.INTERNAL.withDescription(s"Unexpected/handled path ${reqOuter.path}"))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class AkkaGrpcPlugin implements Plugin<Project>, DependencyResolutionListener {
option "generate_client=${extension.generateClient}"
option "generate_server=${extension.generateServer}"
option "server_power_apis=${extension.serverPowerApis}"
option "use_play_actions=${extension.usePlayActions}"
option "logfile=${logFile.getAbsolutePath()}"
if (extension.generatePlay) {
option "generate_play=true"
Expand Down Expand Up @@ -139,6 +140,7 @@ class AkkaGrpcPluginExtension {
boolean generateServer = true
boolean generatePlay = false
boolean serverPowerApis = false
boolean usePlayActions = false

AkkaGrpcPluginExtension(Project project) {
if (project.plugins.hasPlugin("scala"))
Expand Down
11 changes: 10 additions & 1 deletion maven-plugin/src/main/maven/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,15 @@
<required>false</required>
<editable>true</editable>
<description>Whether to generate server "power API" code</description>
</parameter> <parameter>
</parameter>
<parameter>
<name>usePlayActions</name>
<type>boolean</type>
<required>false</required>
<editable>true</editable>
<description>Whether to generate Play router that uses Actions</description>
</parameter>
<parameter>
<name>protoPaths</name>
<type>list</type>
<required>false</required>
Expand All @@ -55,6 +63,7 @@
<generateClient implementation="boolean" default-value="true">${akka-grpc.generate-client}</generateClient>
<generateServer implementation="boolean" default-value="true">${akka-grpc.generate-server}</generateServer>
<serverPowerApis implementation="boolean" default-value="false">${akka-grpc.server-power-apis}</serverPowerApis>
<usePlayActions implementation="boolean" default-value="false">${akka-grpc.use-play-actions}</usePlayActions>
<protoPaths default-value="src/main/proto,src/main/protobuf">${akka-grpc.protoPaths}</protoPaths>
</configuration>
</mojo>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ class GenerateMojo @Inject() (project: MavenProject, buildContext: BuildContext)
var generatePlayServer: Boolean = _
@BeanProperty
var serverPowerApis: Boolean = _
@BeanProperty
var usePlayActions: Boolean = _

override def execute(): Unit = {
val chosenLanguage = parseLanguage(language)
Expand Down Expand Up @@ -134,7 +136,7 @@ class GenerateMojo @Inject() (project: MavenProject, buildContext: BuildContext)
case Java
val glueGenerators = Seq(
if (generateServer) Seq(JavaInterfaceCodeGenerator, JavaServerCodeGenerator(serverPowerApis)) else Seq.empty,
if (generatePlayServer) Seq(JavaInterfaceCodeGenerator, JavaServerCodeGenerator(serverPowerApis), PlayJavaServerCodeGenerator(serverPowerApis)) else Seq.empty,
if (generatePlayServer) Seq(JavaInterfaceCodeGenerator, JavaServerCodeGenerator(serverPowerApis), PlayJavaServerCodeGenerator(serverPowerApis, usePlayActions)) else Seq.empty,
if (serverPowerApis) Seq(JavaPowerApiInterfaceCodeGenerator) else Seq.empty,
if (generateClient) Seq(JavaInterfaceCodeGenerator, JavaClientCodeGenerator) else Seq.empty,
if (generatePlayClient) Seq(JavaInterfaceCodeGenerator, PlayJavaClientCodeGenerator) else Seq.empty
Expand All @@ -144,7 +146,7 @@ class GenerateMojo @Inject() (project: MavenProject, buildContext: BuildContext)
case Scala
val glueGenerators = Seq(
if (generateServer) Seq(ScalaTraitCodeGenerator, ScalaServerCodeGenerator(serverPowerApis)) else Seq.empty,
if (generatePlayServer) Seq(ScalaTraitCodeGenerator, ScalaServerCodeGenerator(serverPowerApis), PlayScalaServerCodeGenerator(serverPowerApis)) else Seq.empty,
if (generatePlayServer) Seq(ScalaTraitCodeGenerator, ScalaServerCodeGenerator(serverPowerApis), PlayScalaServerCodeGenerator(serverPowerApis, usePlayActions)) else Seq.empty,
if (serverPowerApis) Seq(ScalaPowerApiTraitCodeGenerator) else Seq.empty,
if (generateClient) Seq(ScalaTraitCodeGenerator, ScalaClientCodeGenerator) else Seq.empty,
if (generatePlayClient) Seq(ScalaTraitCodeGenerator, PlayScalaClientCodeGenerator) else Seq.empty
Expand Down
Loading

0 comments on commit 114b2f3

Please sign in to comment.