Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Play routers and services #378

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions codegen/src/main/scala/akka/grpc/gen/CodeGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@

package akka.grpc.gen

import akka.grpc.gen.javadsl.Service
import akka.grpc.gen.scaladsl.Service
import akka.grpc.gen.scaladsl.play.PlayScalaClientCodeGenerator
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse
import protocbridge.Artifact

import scala.annotation.tailrec

/**
* Code generator trait that is not directly bound to scala-pb or protoc (other than the types).
*/
Expand All @@ -23,3 +28,28 @@ trait CodeGenerator {
final def run(request: Array[Byte], logger: Logger): Array[Byte] =
run(CodeGeneratorRequest.parseFrom(request), logger: Logger).toByteArray
}

private[gen] object CodeGenerator {

/** Extract the longest common package prefix for a list of packages. */
private[gen] def commonPackage(packages: Seq[String]): String =
packages.reduce(commonPackage(_, _))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old code had an optimisation to check everything for equality. I've moved that optimisation out of this method and into the commonPackage(String,String) method. That means we can use reduce instead of writing our own fold.


/** Extract the longest common package prefix for two packages. */
private[gen] def commonPackage(a: String, b: String): String = {
if (a == b) a else {
val aPackages = a.split('.')
val bPackages = b.split('.')
@tailrec
def countIdenticalPackage(pos: Int): Int = {
if (aPackages(pos) == bPackages(pos)) countIdenticalPackage(pos + 1)
else pos
}

val prefixLength = countIdenticalPackage(0)
if (prefixLength == 0) "" // no common, use root package
else aPackages.take(prefixLength).mkString(".")
}
}

}
4 changes: 4 additions & 0 deletions codegen/src/main/scala/akka/grpc/gen/javadsl/Service.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package akka.grpc.gen.javadsl

import akka.grpc.gen.CodeGenerator
import com.google.protobuf.Descriptors.{ FileDescriptor, ServiceDescriptor }

import scala.collection.JavaConverters._
Expand All @@ -22,4 +23,7 @@ object Service {
fileDesc.getPackage + "." + serviceDescriptor.getName,
serviceDescriptor.getMethods.asScala.map(method ⇒ Method(method)).to[immutable.Seq])
}

private[javadsl] def commonPackage(allServices: Seq[Service]): String =
CodeGenerator.commonPackage(allServices.map(_.packageName))
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@

package akka.grpc.gen.javadsl.play

import akka.grpc.gen.Logger
import akka.grpc.gen.javadsl.{ JavaCodeGenerator, Service }
import akka.grpc.gen.scaladsl.play.PlayScalaClientCodeGenerator
import akka.grpc.gen.{ CodeGenerator, Logger }
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse
import templates.PlayJava.txt.{ AkkaGrpcClientModule, ClientProvider }

import scala.annotation.tailrec
import akka.grpc.gen.scaladsl.play.PlayScalaClientCodeGenerator

object PlayJavaClientCodeGenerator extends PlayJavaClientCodeGenerator

trait PlayJavaClientCodeGenerator extends JavaCodeGenerator {
Expand Down Expand Up @@ -43,13 +40,5 @@ trait PlayJavaClientCodeGenerator extends JavaCodeGenerator {
}

private[play] def packageForSharedModuleFile(allServices: Seq[Service]): String =
// single service or all services in single package - use that
if (allServices.forall(_.packageName == allServices.head.packageName)) allServices.head.packageName
else {
// try to find longest common prefix
allServices.tail.foldLeft(allServices.head.packageName)((packageName, service) =>
if (packageName == service.packageName) packageName
else PlayScalaClientCodeGenerator.commonPackage(packageName, service.packageName))
}

CodeGenerator.commonPackage(allServices.map(_.packageName))
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ package akka.grpc.gen.javadsl.play
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.{ AkkaGrpcServiceModule, Router }

object PlayJavaServerCodeGenerator extends PlayJavaServerCodeGenerator

trait PlayJavaServerCodeGenerator extends JavaCodeGenerator {

val ServiceModuleName = "AkkaGrpcServiceModule"

override def name: String = "akka-grpc-play-server-java"

override def perServiceContent: Set[(Logger, Service) ⇒ CodeGeneratorResponse.File] =
Expand All @@ -20,8 +23,36 @@ trait PlayJavaServerCodeGenerator extends JavaCodeGenerator {
private val generateRouter: (Logger, Service) => CodeGeneratorResponse.File = (logger, service) => {
val b = CodeGeneratorResponse.File.newBuilder()
b.setContent(Router(service).body)
b.setName(s"${service.packageDir}/Abstract${service.name}Router.java")
b.setName(s"${service.packageDir}/${service.name}Router.java")
b.build
}

// FIXME: This code is duplicated for the Scala codegen too
override def staticContent(logger: Logger, allServices: Seq[Service]): Set[CodeGeneratorResponse.File] = {
if (allServices.nonEmpty) {
val packageName = Service.commonPackage(allServices)
val b = CodeGeneratorResponse.File.newBuilder()
b.setContent(AkkaGrpcServiceModule(packageName, allServices).body)
b.setName(s"${packageName.replace('.', '/')}/${ServiceModuleName}.java")
val set = Set(b.build)

logger.info(s"Generated '$packageName.$ServiceModuleName'. \n" +
s"Add 'play.modules.enabled += $packageName.$ServiceModuleName' to your configuration to bind services. " +
s"The following services will be bound: \n" +
allServices.map { service =>
val serviceClass = service.packageName + "." + service.name
s""" * @Named("impl") $serviceClass -> ${serviceClass}Impl\n""" +
s""" - You will need to create the implementation class '${serviceClass}Impl'.\n""" +
s""" - To use a different implementation class, set 'akka.grpc.service."$serviceClass".class' to a new classname.\n""" +
s""" - To disable binding an implementation class, set configuration 'akka.grpc.service."$serviceClass".enabled = false'."""
}.mkString("\n") + "\n" +
"Add the following to your routes file to support all services:\n" +
allServices.map { service =>
val serviceClass = service.packageName + "." + service.name
s"-> / ${serviceClass}Router"
}.mkString("\n"))

set
} else Set.empty
}
}
5 changes: 4 additions & 1 deletion codegen/src/main/scala/akka/grpc/gen/scaladsl/Service.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

package akka.grpc.gen.scaladsl

import akka.grpc.gen.CodeGenerator
import scala.collection.immutable

import scala.collection.JavaConverters._
import com.google.protobuf.Descriptors._
import scalapb.compiler.{ DescriptorPimps, GeneratorParams }
Expand All @@ -30,4 +30,7 @@ object Service {
fileDesc.getPackage + "." + serviceDescriptor.getName,
serviceDescriptor.getMethods.asScala.map(method ⇒ Method(method)).to[immutable.Seq])
}

private[scaladsl] def commonPackage(allServices: Seq[Service]): String =
CodeGenerator.commonPackage(allServices.map(_.packageName))
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

package akka.grpc.gen.scaladsl.play

import akka.grpc.gen.Logger
import akka.grpc.gen.scaladsl.{ ScalaCodeGenerator, Service }
import akka.grpc.gen.{ CodeGenerator, Logger }
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse
import templates.PlayScala.txt.{ AkkaGrpcClientModule, ClientProvider }

Expand All @@ -30,7 +30,7 @@ trait PlayScalaClientCodeGenerator extends ScalaCodeGenerator {

override def staticContent(logger: Logger, allServices: Seq[Service]): Set[CodeGeneratorResponse.File] = {
if (allServices.nonEmpty) {
val packageName = packageForSharedModuleFile(allServices)
val packageName = Service.commonPackage(allServices)
val b = CodeGeneratorResponse.File.newBuilder()
b.setContent(AkkaGrpcClientModule(packageName, allServices).body)
b.setName(s"${packageName.replace('.', '/')}/${ClientModuleName}.scala")
Expand All @@ -42,30 +42,4 @@ trait PlayScalaClientCodeGenerator extends ScalaCodeGenerator {
} else Set.empty
}

def packageForSharedModuleFile(allServices: Seq[Service]): String =
// single service or all services in single package - use that
if (allServices.forall(_.packageName == allServices.head.packageName)) allServices.head.packageName
else {
// try to find longest common prefix
allServices.tail.foldLeft(allServices.head.packageName)((packageName, service) =>
if (packageName == service.packageName) packageName
else commonPackage(packageName, service.packageName))
}

/** extract the longest common package prefix for two classes */
def commonPackage(a: String, b: String): String = {
val aPackages = a.split('.')
val bPackages = b.split('.')
@tailrec
def countIdenticalPackage(pos: Int): Int = {
if (aPackages(pos) == bPackages(pos)) countIdenticalPackage(pos + 1)
else pos
}

val prefixLength = countIdenticalPackage(0)
if (prefixLength == 0) "" // no common, use root package
else aPackages.take(prefixLength).mkString(".")

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ package akka.grpc.gen.scaladsl.play
import akka.grpc.gen.Logger
import akka.grpc.gen.scaladsl.{ ScalaCodeGenerator, Service }
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse
import templates.PlayScala.txt.Router
import templates.PlayScala.txt.{ AkkaGrpcClientModule, AkkaGrpcServiceModule, Router }

object PlayScalaServerCodeGenerator extends PlayScalaServerCodeGenerator

trait PlayScalaServerCodeGenerator extends ScalaCodeGenerator {

val ServiceModuleName = "AkkaGrpcServiceModule"

override def name: String = "akka-grpc-play-server-scala"

override def perServiceContent = super.perServiceContent + generateRouter
Expand All @@ -22,4 +25,33 @@ trait PlayScalaServerCodeGenerator extends ScalaCodeGenerator {
b.setName(s"${service.packageDir}/Abstract${service.name}Router.scala")
b.build
}

override def staticContent(logger: Logger, allServices: Seq[Service]): Set[CodeGeneratorResponse.File] = {
if (allServices.nonEmpty) {
val packageName = Service.commonPackage(allServices)
val b = CodeGeneratorResponse.File.newBuilder()
b.setContent(AkkaGrpcServiceModule(packageName, allServices).body)
b.setName(s"${packageName.replace('.', '/')}/${ServiceModuleName}.scala")
val set = Set(b.build)

logger.info(s"Generated '$packageName.$ServiceModuleName'. \n" +
s"Add 'play.modules.enabled += $packageName.$ServiceModuleName' to your configuration to bind services. " +
s"The following services will be bound: \n" +
allServices.map { service =>
val serviceClass = service.packageName + "." + service.name
s""" * @Named("impl") $serviceClass -> ${serviceClass}Impl\n""" +
s""" - You will need to create the implementation class '${serviceClass}Impl'.\n""" +
s""" - To use a different implementation class, set 'akka.grpc.service."$serviceClass".class' to a new classname.\n""" +
s""" - To disable binding an implementation class, set configuration 'akka.grpc.service."$serviceClass".enabled = false'."""
}.mkString("\n") + "\n" +
"Add the following to your routes file to support all services:\n" +
allServices.map { service =>
val serviceClass = service.packageName + "." + service.name
s"-> / ${serviceClass}Router"
}.mkString("\n"))

set
} else Set.empty
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class AkkaGrpcClientModule extends Module {
return seq(
@services.map { service =>
bind(@{service.name}Client.class).toProvider(@{service.name}ClientProvider.class)
}.mkString(",")
}.mkString(", ")
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@*
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
*@

@(packageName: String, services: Seq[akka.grpc.gen.javadsl.Service])

@akka.grpc.gen.Constants.DoNotEditComment
package @{packageName};

import play.api.Configuration;
import play.api.Environment;
import play.api.inject.Binding;
import play.api.inject.Module;
import scala.collection.Seq;
@services.map { service =>
import @{service.packageName}.*;
}

import akka.grpc.scaladsl.play.AbstractAkkaGrpcServiceModule;

/**
* Add this generated AkkaGrpcServiceModule to play.modules.enabled
* in your application.conf to have the available gRPC services injectable
*/
public class AkkaGrpcServiceModule extends AbstractAkkaGrpcServiceModule {
@@Override
public Seq<Class<?>> services() {
return classSeq(
@services.map { service =>
@{service.name}.class
}.mkString(",")
);
}
}
25 changes: 11 additions & 14 deletions codegen/src/main/twirl/templates/PlayJavaServer/Router.scala.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
package @service.packageName;

import java.util.concurrent.CompletionStage;
import javax.inject.Inject;
import javax.inject.Named;

import akka.japi.Function;
import scala.concurrent.Future;
Expand All @@ -17,25 +19,20 @@ import akka.http.scaladsl.model.HttpResponse;
import akka.stream.Materializer;

import akka.grpc.internal.PlayRouter;
import akka.grpc.internal.PlayRouterHelper;

/**
* Abstract base class for implementing @{service.name} in Java and using as a play Router
*/
public abstract class Abstract@{service.name}Router extends PlayRouter implements @{service.name} {

public Abstract@{service.name}Router(Materializer mat) {
super(mat, @{service.name}.name);
}

/**
* INTERNAL API
*/
final public scala.Function1<HttpRequest, Future<HttpResponse>> createHandler(String prefix, Materializer mat) {
return akka.grpc.internal.PlayRouterHelper.handlerFor(
@{service.name}HandlerFactory.create(this, prefix, mat)
);
public class @{service.name}Router extends PlayRouter {

@@Inject
public @{service.name}Router(@@Named("impl") @{service.name} service, Materializer mat) {
super(
'/' + @{service.name}.name,
PlayRouterHelper.handlerFor(@{service.name}HandlerFactory.create(service, @{service.name}.name, mat))
);
}


}

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@*
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
*@

@(packageName: String, services: Seq[akka.grpc.gen.scaladsl.Service])

@akka.grpc.gen.Constants.DoNotEditComment
package @{packageName}

import akka.grpc.scaladsl.play.AbstractAkkaGrpcServiceModule
import play.api.inject.Binding
import play.api.{Configuration, Environment}
@services.map { service =>
import @{service.packageName}._
}

/**
* Add this generated AkkaGrpcServiceModule to play.modules.enabled
* in your application.conf to have the available gRPC services injectable
*/
class AkkaGrpcServiceModule extends AbstractAkkaGrpcServiceModule {
override protected def services: Seq[Class[_]] = {
Seq(
@services.map { service =>
classOf[@{service.name}],
}
)
}
}
Loading