diff --git a/build.sbt b/build.sbt index 03d8c89f6962063d28639c1d9428a2318dbcb176..40c4cdb596565d8f80b127311b34b8154ad672df 100644 --- a/build.sbt +++ b/build.sbt @@ -19,14 +19,14 @@ lazy val root = (project in file(".")) libraryDependencies ++= Seq( "org.scala-lang" % "scala-compiler" % scalaVersion.value, "org.scala-lang" % "scala-reflect" % scalaVersion.value, - "com.alipay.sofa" % "jraft-core" % "1.3.9", "com.typesafe.scala-logging" %% "scala-logging" % "3.9.4", "org.apache.logging.log4j" % "log4j-api" % "2.14.1" % Test, "org.apache.logging.log4j" % "log4j-core" % "2.14.1" % Test, "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.14.1" % Test, "com.typesafe.play" %% "play-json" % "2.7.4" % Test, "org.scalatest" %% "scalatest" % "3.2.10" % Test, - "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.13.0" % Test + "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.13.0" % Test, + "com.alipay.sofa" % "jraft-core" % "1.3.9" % Test ), Compile / scalacOptions ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, n)) if n <= 12 => Nil diff --git a/src/main/scala/io/github/dreamylost/ProcessorCreator.scala b/src/main/scala/io/github/dreamylost/ProcessorCreator.scala new file mode 100644 index 0000000000000000000000000000000000000000..eac8a3e23ac0b88233a5f13f3609cea639b7902c --- /dev/null +++ b/src/main/scala/io/github/dreamylost/ProcessorCreator.scala @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 jxnu-liguobin && contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.dreamylost + +import io.github.dreamylost.macros.ProcessorCreatorMacro + +import java.util.concurrent.Executor + +/** + * The macro util to generate processor for alipay sofa jraft rpc. + * + * @author 梦境迷离 + * @version 1.0,2021/12/6 + */ +object ProcessorCreator { + + /** + * + * @param service Instance of the [[Service]] + * @param defaultResp Default instance of the Response Message + * @param executor Instance of the Executor + * @param processRequest Function to handle request + * @param processException Function to handle exception + * @tparam RRC RpcRequestClosure + * @tparam RRP RpcRequestProcessor + * @tparam RC RpcContext + * @tparam Req Request Message of the protobuf + * @tparam Resp Response Message of the protobuf + * @tparam Service Should be custom class/interface/trait which handle the business logic of Processors + * @tparam E Should be subclass of the Executor + * @return [[ RRP ]] Instance of the RpcRequestProcessor subclass + */ + def apply[RRC, RRP[_ <: Req], RC, Req, Resp, Service, E <: Executor] + ( + defaultResp: Resp, + processRequest: (Service, RRC, Req) ⇒ Resp, + processException: (Service, RC, Exception) ⇒ Resp + )(implicit service: Service, executor: E): RRP[Req] = macro ProcessorCreatorMacro.SimpleImpl[RRC, RRP[_ <: Req], RC, Req, Resp, Service, E] + + def apply[RRC, RRP[_ <: Req], RC, Req, Resp, Service] + ( + processRequest: (Service, RRC, Req) ⇒ Resp, + processException: (Service, RC, Exception) ⇒ Resp + )(implicit service: Service): RRP[Req] = macro ProcessorCreatorMacro.WithoutExecutorAndDefaultResp[RRC, RRP[_ <: Req], RC, Req, Resp, Service] + + /** + * Having two identical type parameters will cause the compiler to recognize error and change the order of generics to avoid. + */ + def apply[Service, RRC, RRP[_ <: Req], RC, Req, Resp] + ( + processRequest: (Service, RRC, Req) ⇒ Resp, + processException: (Service, RC, Exception) ⇒ Resp + ): RRP[Req] = macro ProcessorCreatorMacro.OnlyWithFunctionalParameters[Service, RRC, RRP[_ <: Req], RC, Req, Resp] +} diff --git a/src/main/scala/io/github/dreamylost/macros/Creator.scala b/src/main/scala/io/github/dreamylost/macros/Creator.scala index eb1bfa253b4eab593d0b19537f0ecd108a4500a8..2eccb2c1ac8e10a0c70b54ff8176ecc00385eb71 100644 --- a/src/main/scala/io/github/dreamylost/macros/Creator.scala +++ b/src/main/scala/io/github/dreamylost/macros/Creator.scala @@ -22,7 +22,6 @@ package io.github.dreamylost.macros import scala.reflect.runtime.currentMirror -import scala.reflect.runtime.universe.typeTag import scala.reflect.runtime.universe._ /** diff --git a/src/main/scala/io/github/dreamylost/macros/MacroCache.scala b/src/main/scala/io/github/dreamylost/macros/MacroCache.scala index 7409a490eb34464a3901df49a3091b40aedfcf6a..a119834d92ccc9823885eacf9f359ba81de37df9 100644 --- a/src/main/scala/io/github/dreamylost/macros/MacroCache.scala +++ b/src/main/scala/io/github/dreamylost/macros/MacroCache.scala @@ -20,7 +20,6 @@ */ package io.github.dreamylost.macros -import scala.collection.mutable object MacroCache { diff --git a/src/main/scala/io/github/dreamylost/macros/ProcessorCreatorMacro.scala b/src/main/scala/io/github/dreamylost/macros/ProcessorCreatorMacro.scala new file mode 100644 index 0000000000000000000000000000000000000000..43209291e35b25eaf59dcd365bac014445a8a79c --- /dev/null +++ b/src/main/scala/io/github/dreamylost/macros/ProcessorCreatorMacro.scala @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2021 jxnu-liguobin && contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.dreamylost.macros + +import scala.reflect.macros.blackbox +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +/** + * + * @author 梦境迷离 + * @version 1.0,2021/12/6 + */ +object ProcessorCreatorMacro { + + private val classNamePrefix: String = "AnonProcessor$" + + def SimpleImpl[RRC: c.WeakTypeTag, RRP: c.WeakTypeTag, RC: c.WeakTypeTag, Req: c.WeakTypeTag, Resp: c.WeakTypeTag, Service: c.WeakTypeTag, E: c.WeakTypeTag] + (c: blackbox.Context) + ( + defaultResp: c.Expr[Req], + processRequest: c.Expr[(Service, RRC, Req) ⇒ Req], + processException: c.Expr[(Service, RC, Exception) ⇒ Req] + )(service: c.Expr[Service], executor: c.Expr[E]): c.Expr[RRP] = { // parameters in order, parameter names differ will compile error + import c.universe._ + val serviceType = weakTypeOf[Service] + val className = TypeName(classNamePrefix + MacroCache.getIdentityId) + val reqProtoType = weakTypeOf[Req] + val rpcRequestClosureType = weakTypeOf[RRC] + val rpcContextType = weakTypeOf[RC] + val respProtoType = weakTypeOf[Resp] + val processor = + q""" + class $className(private val service: $serviceType, executor: java.util.concurrent.Executor = null) + extends com.alipay.sofa.jraft.rpc.RpcRequestProcessor[$reqProtoType](executor, $defaultResp) with com.typesafe.scalalogging.LazyLogging { + override def handleRequest(rpcCtx: com.alipay.sofa.jraft.rpc.RpcContext, request: $reqProtoType) { + try { + val msg = processRequest(request, new com.alipay.sofa.jraft.rpc.RpcRequestClosure(rpcCtx, this.defaultResp)) + if (msg != null) { + rpcCtx.sendResponse(msg) + } + } catch { + case e: Exception => + logger.error("handleRequest" + request + "failed", e) + rpcCtx.sendResponse(processError(rpcCtx, e)) + } + } + override def interest(): String = classOf[$reqProtoType].getName + + def processRequest(request: $reqProtoType, done: $rpcRequestClosureType): $respProtoType = { + $processRequest(service, done, request) + } + + def processError(rpcCtx: $rpcContextType, exception: Exception): $respProtoType = { + $processException(service, rpcCtx, exception) + } + } + new $className($service, $executor) + """ + printTree[RRP](c)(processor) + } + + def WithoutExecutorAndDefaultResp[RRC: c.WeakTypeTag, RRP: c.WeakTypeTag, RC: c.WeakTypeTag, Req: c.WeakTypeTag, Resp: c.WeakTypeTag, Service: c.WeakTypeTag] + (c: blackbox.Context)( + processRequest: c.Expr[(Service, RRC, Req) ⇒ Req], + processException: c.Expr[(Service, RC, Exception) ⇒ Req] + )(service: c.Expr[Service]): c.Expr[RRP] = { + import c.universe._ + checkTree[RRC, RRP, RC, Service](c) + val serviceType = weakTypeOf[Service] + val className = TypeName(classNamePrefix + MacroCache.getIdentityId) + val reqProtoType = weakTypeOf[Req] + val rpcRequestClosureType = weakTypeOf[RRC] + val rpcContextType = weakTypeOf[RC] + val respProtoType = weakTypeOf[Resp] + val respProtoCompanionType = weakTypeOf[Resp].companion //getDefaultInstance is static method, it's in companion + val processor = + q""" + class $className(private val service: $serviceType, executor: java.util.concurrent.Executor = null) + extends com.alipay.sofa.jraft.rpc.RpcRequestProcessor[$reqProtoType](executor, $respProtoCompanionType.getDefaultInstance) + with com.typesafe.scalalogging.LazyLogging { + override def handleRequest(rpcCtx: com.alipay.sofa.jraft.rpc.RpcContext, request: $reqProtoType) { + try { + val msg = processRequest(request, new com.alipay.sofa.jraft.rpc.RpcRequestClosure(rpcCtx, this.defaultResp)) + if (msg != null) { + rpcCtx.sendResponse(msg) + } + } catch { + case e: Exception => + logger.error("handleRequest" + request + "failed", e) + rpcCtx.sendResponse(processError(rpcCtx, e)) + } + } + override def interest(): String = classOf[$reqProtoType].getName + + def processRequest(request: $reqProtoType, done: $rpcRequestClosureType): $respProtoType = { + $processRequest(service, done, request) + } + + def processError(rpcCtx: $rpcContextType, exception: Exception): $respProtoType = { + $processException(service, rpcCtx, exception) + } + } + new $className($service) + """ + printTree[RRP](c)(processor) + } + + def OnlyWithFunctionalParameters[Service: c.WeakTypeTag, RRC: c.WeakTypeTag, RRP: c.WeakTypeTag, RC: c.WeakTypeTag, Req: c.WeakTypeTag, Resp: c.WeakTypeTag] + (c: blackbox.Context)( + processRequest: c.Expr[(Service, RRC, Req) ⇒ Req], + processException: c.Expr[(Service, RC, Exception) ⇒ Req] + ): c.Expr[RRP] = { + import c.universe._ + checkTree[RRC, RRP, RC, Service](c) + val serviceType = weakTypeOf[Service] + val className = TypeName(classNamePrefix + MacroCache.getIdentityId) + val reqProtoType = weakTypeOf[Req] + val rpcRequestClosureType = weakTypeOf[RRC] + val rpcContextType = weakTypeOf[RC] + val respProtoType = weakTypeOf[Resp] + val respProtoCompanionType = weakTypeOf[Resp].companion //getDefaultInstance is static method, it's in companion + val processor = + q""" + class $className(private val service: $serviceType, executor: java.util.concurrent.Executor = null) + extends com.alipay.sofa.jraft.rpc.RpcRequestProcessor[$reqProtoType](executor, $respProtoCompanionType.getDefaultInstance) + with com.typesafe.scalalogging.LazyLogging { + override def handleRequest(rpcCtx: com.alipay.sofa.jraft.rpc.RpcContext, request: $reqProtoType) { + try { + val msg = processRequest(request, new com.alipay.sofa.jraft.rpc.RpcRequestClosure(rpcCtx, this.defaultResp)) + if (msg != null) { + rpcCtx.sendResponse(msg) + } + } catch { + case e: Exception => + logger.error("handleRequest" + request + "failed", e) + rpcCtx.sendResponse(processError(rpcCtx, e)) + } + } + override def interest(): String = classOf[$reqProtoType].getName + + def processRequest(request: $reqProtoType, done: $rpcRequestClosureType): $respProtoType = { + $processRequest(service, done, request) + } + + def processError(rpcCtx: $rpcContextType, exception: Exception): $respProtoType = { + $processException(service, rpcCtx, exception) + } + } + val service = new io.github.dreamylost.macros.Creator[$serviceType].createInstance(null)(0) + new $className(service) + """ + printTree[RRP](c)(processor) + } + + private def printTree[RRP](c: blackbox.Context)(processor: c.Tree): c.Expr[RRP] = { + val ret = c.Expr[RRP](processor) + c.info( + c.enclosingPosition, + s"\n###### Time: ${ + ZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME) + } " + + s"Expanded macro start ######\n" + ret.toString() + "\n###### Expanded macro end ######\n", + force = true + ) + ret + } + + private def checkTree[RRC: c.WeakTypeTag, RRP: c.WeakTypeTag, RC: c.WeakTypeTag, Service: c.WeakTypeTag](c: blackbox.Context): Unit = { + import c.universe._ + val serviceType = weakTypeOf[Service] + if (serviceType.typeSymbol.isAbstract || !serviceType.typeSymbol.isClass) { + c.abort(c.enclosingPosition, "Not support for abstract classes") + } + if (serviceType.typeSymbol.isModuleClass) { + c.abort(c.enclosingPosition, "Not support for module classes") + } + val rpcRequestClosureType = weakTypeOf[RRC] + if (!rpcRequestClosureType.resultType.toString.equals("com.alipay.sofa.jraft.rpc.RpcRequestClosure")) { + c.abort(c.enclosingPosition, s"`RRC` only support for `com.alipay.sofa.jraft.rpc.RpcRequestClosure`, not `${rpcRequestClosureType.resultType.toString}`") + } + val rpcContextType = weakTypeOf[RC] + if (!rpcContextType.resultType.toString.equals("com.alipay.sofa.jraft.rpc.RpcContext")) { + c.abort(c.enclosingPosition, s"`RRC` only support for `com.alipay.sofa.jraft.rpc.RpcContext`, not `${rpcContextType.resultType.toString}`") + } + } +} diff --git a/src/main/scala/io/github/dreamylost/sofa/CustomRpcProcessor.scala b/src/main/scala/io/github/dreamylost/sofa/CustomRpcProcessor.scala deleted file mode 100644 index a4baf606484728314c8ef0dcd7c9bcbde1e7df9b..0000000000000000000000000000000000000000 --- a/src/main/scala/io/github/dreamylost/sofa/CustomRpcProcessor.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2021 jxnu-liguobin && contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package io.github.dreamylost.sofa - -import com.alipay.sofa.jraft.rpc.{ RpcContext, RpcRequestClosure, RpcRequestProcessor } -import com.google.protobuf.Message -import com.typesafe.scalalogging.LazyLogging - -import java.util.concurrent.Executor -import scala.reflect.ClassTag - -/** - * Common processor - * - * @param executor The executor used to execute the specified sofa RPC request - * @param defaultResp Default message instance for sofa - * @tparam Req The Request proto message for sofa - * @author 梦境迷离 - * @version 1.0,2021/12/3 - */ -abstract class CustomRpcProcessor[Req <: Message](executor: Executor, override val defaultResp: Message)(implicit reqClassTag: ClassTag[Req]) - extends RpcRequestProcessor[Req](executor, defaultResp) with LazyLogging { - - override def handleRequest(rpcCtx: RpcContext, request: Req) { - try { - val msg = processRequest(request, new RpcRequestClosure(rpcCtx, this.defaultResp)) - if (msg != null) { - rpcCtx.sendResponse(msg) - } - } catch { - case e: Exception => - logger.error(s"handleRequest $request failed", e) - rpcCtx.sendResponse(processError(rpcCtx, e)) - } - } - - def processError(rpcCtx: RpcContext, exception: Exception): Message - - override def interest(): String = { - reqClassTag.runtimeClass.getName - } -} diff --git a/src/main/scala/io/github/dreamylost/sofa/Processable.scala b/src/main/scala/io/github/dreamylost/sofa/Processable.scala deleted file mode 100644 index ccc8e7f619edcf8880a38b03d3f4ea1f1d8bf91d..0000000000000000000000000000000000000000 --- a/src/main/scala/io/github/dreamylost/sofa/Processable.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2021 jxnu-liguobin && contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package io.github.dreamylost.sofa - -import com.alipay.sofa.jraft.rpc.{ RpcContext, RpcRequestClosure } -import com.google.protobuf.Message - -/** - * The Processable util to generate processor for alipay sofa jraft rpc. - * - * @author 梦境迷离 - * @version 1.0,2021/12/3 - */ -object Processable { - - def apply[Req <: Message, Service, Executor <: java.util.concurrent.Executor] - (service: Service, defaultResp: Message, executor: Executor) - ( - processRequest: (Service, RpcRequestClosure, Req) ⇒ Message, - processException: (Service, RpcContext, Exception) ⇒ Message): CustomRpcProcessor[Req] = macro ProcessableMacro.processorImpl[Req, Service, Executor] - - def apply[Service, Req <: Message, Resp <: Message](service: Service) - ( - processRequest: (Service, RpcRequestClosure, Req) ⇒ Message, - processException: (Service, RpcContext, Exception) ⇒ Message): CustomRpcProcessor[Req] = macro ProcessableMacro.processorWithDefaultRespImpl[Service, Req, Resp] - - def apply[Req <: Message, Resp <: Message, Service] - ( - processRequest: (Service, RpcRequestClosure, Req) ⇒ Message, - processException: (Service, RpcContext, Exception) ⇒ Message): CustomRpcProcessor[Req] = macro ProcessableMacro.processorWithDefaultRespServiceImpl[Req, Resp, Service] - -} - diff --git a/src/main/scala/io/github/dreamylost/sofa/ProcessableMacro.scala b/src/main/scala/io/github/dreamylost/sofa/ProcessableMacro.scala deleted file mode 100644 index a250ce37412d8ac6bfdb39d159517d08c2a72c7d..0000000000000000000000000000000000000000 --- a/src/main/scala/io/github/dreamylost/sofa/ProcessableMacro.scala +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2021 jxnu-liguobin && contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package io.github.dreamylost.sofa - -import com.alipay.sofa.jraft.rpc.{ RpcContext, RpcRequestClosure } -import com.google.protobuf.Message -import io.github.dreamylost.macros.MacroCache - -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import scala.reflect.macros.blackbox - -/** - * Processable macro - * - * @author 梦境迷离 - * @since 2021/12/4 - * @version 1.0 - */ -object ProcessableMacro { - - private val classNamePrefix: String = "AnonProcessor$" - - def processorWithDefaultRespServiceImpl[Req <: Message: c.WeakTypeTag, Resp <: Message: c.WeakTypeTag, Service: c.WeakTypeTag] - (c: blackbox.Context) - ( - processRequest: c.Expr[(Service, RpcRequestClosure, Req) ⇒ Req], - processException: c.Expr[(Service, RpcContext, Exception) ⇒ Req] - ): c.Expr[CustomRpcProcessor[Req]] = { - import c.universe._ - val serviceType = weakTypeOf[Service] - if (serviceType.typeSymbol.isAbstract || !serviceType.typeSymbol.isClass) { - c.abort(c.enclosingPosition, "Not support for abstract classes") - } - if (serviceType.typeSymbol.isModuleClass) { - c.abort(c.enclosingPosition, "Not support for module classes") - } - val className = TypeName(classNamePrefix + MacroCache.getIdentityId) - val reqProtoType = weakTypeOf[Req] - val respProtoType = weakTypeOf[Resp].companion //getDefaultInstance is static method, it's in companion - val processor = - q""" - class $className(private val service: $serviceType, executor: java.util.concurrent.Executor = null) - extends io.github.dreamylost.sofa.CustomRpcProcessor[$reqProtoType](executor, $respProtoType.getDefaultInstance) { - - override def processRequest(request: $reqProtoType, done: com.alipay.sofa.jraft.rpc.RpcRequestClosure): com.google.protobuf.Message = { - $processRequest(service, done, request) - } - - override def processError(rpcCtx: com.alipay.sofa.jraft.rpc.RpcContext, exception: Exception): com.google.protobuf.Message = { - $processException(service, rpcCtx, exception) - } - } - val service = new io.github.dreamylost.macros.Creator[$serviceType].createInstance(null)(0) - new $className(service, null) - """ - printTree[Req](c)(processor) - } - - def processorWithDefaultRespImpl[Service: c.WeakTypeTag, Req <: Message: c.WeakTypeTag, Resp <: Message: c.WeakTypeTag] - (c: blackbox.Context) - (service: c.Expr[Service]) - ( - processRequest: c.Expr[(Service, RpcRequestClosure, Req) ⇒ Req], - processException: c.Expr[(Service, RpcContext, Exception) ⇒ Req] - ): c.Expr[CustomRpcProcessor[Req]] = { - import c.universe._ - val className = TypeName(classNamePrefix + MacroCache.getIdentityId) - val serviceType = weakTypeOf[Service] - val reqProtoType = weakTypeOf[Req] - val respProtoType = weakTypeOf[Resp].companion //getDefaultInstance is static method, it's in companion - val processor = - q""" - class $className(private val service: $serviceType, executor: java.util.concurrent.Executor = null) - extends io.github.dreamylost.sofa.CustomRpcProcessor[$reqProtoType](executor, $respProtoType.getDefaultInstance) { - - override def processRequest(request: $reqProtoType, done: com.alipay.sofa.jraft.rpc.RpcRequestClosure): com.google.protobuf.Message = { - $processRequest(service, done, request) - } - - override def processError(rpcCtx: com.alipay.sofa.jraft.rpc.RpcContext, exception: Exception): com.google.protobuf.Message = { - $processException(service, rpcCtx, exception) - } - } - new $className($service, null) - """ - printTree[Req](c)(processor) - } - - def processorImpl[Req <: Message: c.WeakTypeTag, Service: c.WeakTypeTag, Executor: c.WeakTypeTag] - (c: blackbox.Context) - ( - service: c.Expr[Service], - defaultResp: c.Expr[Req], - executor: c.Expr[Executor] - ) - ( - processRequest: c.Expr[(Service, RpcRequestClosure, Req) ⇒ Req], - processException: c.Expr[(Service, RpcContext, Exception) ⇒ Req] - ): c.Expr[CustomRpcProcessor[Req]] = { - import c.universe._ - val className = TypeName(classNamePrefix + MacroCache.getIdentityId) - val serviceType = weakTypeOf[Service] - val requestProtoType = weakTypeOf[Req] - val processor = - q""" - class $className(private val service: $serviceType, executor: java.util.concurrent.Executor = null) - extends io.github.dreamylost.sofa.CustomRpcProcessor[$requestProtoType](executor, $defaultResp) { - - override def processRequest(request: $requestProtoType, done: com.alipay.sofa.jraft.rpc.RpcRequestClosure): com.google.protobuf.Message = { - $processRequest(service, done, request) - } - - override def processError(rpcCtx: com.alipay.sofa.jraft.rpc.RpcContext, exception: Exception): com.google.protobuf.Message = { - $processException(service, rpcCtx, exception) - } - } - new $className($service, $executor) - """ - printTree[Req](c)(processor) - } - - private def printTree[Req <: Message](c: blackbox.Context)(processor: c.Tree): c.Expr[CustomRpcProcessor[Req]] = { - val ret = c.Expr[CustomRpcProcessor[Req]](processor) - c.info( - c.enclosingPosition, - s"\n###### Time: ${ - ZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME) - } " + - s"Expanded macro start ######\n" + ret.toString() + "\n###### Expanded macro end ######\n", - force = true - ) - ret - } -} diff --git a/src/test/proto/schema.proto b/src/test/proto/schema.proto index 2155679528954c46a42bc0606109ce8c0514025e..928824306e0fc06112f5b6e550fd292da3c40b22 100644 --- a/src/test/proto/schema.proto +++ b/src/test/proto/schema.proto @@ -1,4 +1,4 @@ -//Test for Processable +//Test for Processable and ProcessorCreator syntax = "proto3"; option java_package = "io.github.dreamylost.test.proto"; diff --git a/src/test/scala/io/github/dreamylost/ProcessableTest.scala b/src/test/scala/io/github/dreamylost/ProcessableTest.scala deleted file mode 100644 index d54c19d9e7e736aa0185bc4ddf7ae56ca5f09d5f..0000000000000000000000000000000000000000 --- a/src/test/scala/io/github/dreamylost/ProcessableTest.scala +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2021 jxnu-liguobin && contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package io.github.dreamylost - -import com.alipay.sofa.jraft.rpc.{ RpcContext, RpcRequestClosure } -import io.github.dreamylost.test.proto.BOpenSession.{ BOpenSessionReq, BOpenSessionResp } -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -import java.util.concurrent.Executor -import io.github.dreamylost.sofa.Processable -import scala.jdk.CollectionConverters.MapHasAsScala -import io.github.dreamylost.sofa.CustomRpcProcessor - -/** - * - * @author 梦境迷离 - * @version 1.0,2021/12/3 - */ -class ProcessableTest extends AnyFlatSpec with Matchers { - - // origin - "Processable1" should "compile ok" in { - val openSession: CustomRpcProcessor[BOpenSessionReq] = Processable[BOpenSessionReq, NetService, Executor]( - new NetService, BOpenSessionResp.getDefaultInstance, (command: Runnable) => ??? - )( - (service, rpcRequestClosure, req) => { - import scala.jdk.CollectionConverters.MapHasAsScala - val username = req.getUsername - val password = req.getPassword - val configurationMap = req.getConfigurationMap - val ret = service.openSession(username, password, configurationMap.asScala.toMap) - BOpenSessionResp.newBuilder().setSessionHandle(ret).build() - }, - (service, rpcContext, exception) => { - BOpenSessionResp.newBuilder().setStatus(exception.getLocalizedMessage).build() - } - ) - - println(openSession.defaultResp) - - println(openSession.getClass.getClass.getName) - - println(openSession.interest()) - } - - // // simple v1 - "Processable2" should "compile ok" in { - val openSession: CustomRpcProcessor[BOpenSessionReq] = Processable[NetService, BOpenSessionReq, BOpenSessionResp](new NetService)( - (service, _, req) => { - val username = req.getUsername - val password = req.getPassword - val configurationMap = req.getConfigurationMap - val ret = service.openSession(username, password, configurationMap.asScala.toMap) - BOpenSessionResp.newBuilder().setSessionHandle(ret).build() - }, - (_, _, exception) => { - BOpenSessionResp.newBuilder().setStatus(exception.getLocalizedMessage).build() - } - ) - - println(openSession.defaultResp) - - println(openSession.getClass.getClass.getName) - - println(openSession.interest()) - } - - // simple v2 - "Processable3" should "compile ok" in { - // NetService must be a class and with an no parameter construction - val openSession: CustomRpcProcessor[BOpenSessionReq] = Processable[BOpenSessionReq, BOpenSessionResp, NetService]( - (service: NetService, rpc: RpcRequestClosure, req: BOpenSessionReq) => { - import scala.jdk.CollectionConverters.MapHasAsScala - val username = req.getUsername - val password = req.getPassword - val configurationMap = req.getConfigurationMap - val ret = service.openSession(username, password, configurationMap.asScala.toMap) - BOpenSessionResp.newBuilder().setSessionHandle(ret).build() - }, - (service: NetService, rpc: RpcContext, exception: Exception) => { - BOpenSessionResp.newBuilder().setStatus(exception.getLocalizedMessage).build() - } - ) - - println(openSession.defaultResp) - - println(openSession.getClass.getClass.getName) - - println(openSession.interest()) - } - - // simple v2 - "Processable4" should "compile ok" in { - // NetService must be a class and with an no parameter construction - val openSession: CustomRpcProcessor[BOpenSessionReq] = Processable[BOpenSessionReq, BOpenSessionResp, NetService]( - (service, rpc, req) => { - val username = req.getUsername - val password = req.getPassword - val configurationMap = req.getConfigurationMap - val ret = service.openSession(username, password, configurationMap.asScala.toMap) - BOpenSessionResp.newBuilder().setSessionHandle(ret).build() - }, - (service, rpc, exception) => { - BOpenSessionResp.newBuilder().setStatus(exception.getLocalizedMessage).build() - } - ) - - println(openSession.defaultResp) - - println(openSession.getClass.getClass.getName) - - println(openSession.interest()) - } - -} diff --git a/src/test/scala/io/github/dreamylost/ProcessorCreatorTest.scala b/src/test/scala/io/github/dreamylost/ProcessorCreatorTest.scala new file mode 100644 index 0000000000000000000000000000000000000000..3eb9e2c082edaa0f195a34f424ca5c1504675cd4 --- /dev/null +++ b/src/test/scala/io/github/dreamylost/ProcessorCreatorTest.scala @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2021 jxnu-liguobin && contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.dreamylost + +import com.alipay.sofa.jraft.rpc.{ RpcContext, RpcRequestClosure, RpcRequestProcessor } +import io.github.dreamylost.test.proto.BOpenSession.{ BOpenSessionReq, BOpenSessionResp } +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +import java.util.concurrent.Executor + +/** + * + * @author 梦境迷离 + * @version 1.0,2021/12/6 + */ +class ProcessorCreatorTest extends AnyFlatSpec with Matchers { + + // please exec `sbt compile` to generate java class fof the protobuf + // origin + "ProcessorCreator1" should "compile ok" in { + implicit val service = new NetService + implicit val executor: Executor = new Executor { + override def execute(command: Runnable): Unit = () + } + val openSession = ProcessorCreator[RpcRequestClosure, RpcRequestProcessor, RpcContext, BOpenSessionReq, BOpenSessionResp, NetService, Executor]( + BOpenSessionResp.getDefaultInstance, + (service, rpcRequestClosure, req) => { + import scala.jdk.CollectionConverters.MapHasAsScala + val username = req.getUsername + val password = req.getPassword + val configurationMap = req.getConfigurationMap + val ret = service.openSession(username, password, configurationMap.asScala.toMap) + BOpenSessionResp.newBuilder().setSessionHandle(ret).build() + }, + (service, rpcContext, exception) => { + BOpenSessionResp.newBuilder().setStatus(exception.getLocalizedMessage).build() + } + ) + + println(openSession.defaultResp) + + println(openSession.getClass.getClass.getName) + + println(openSession.interest()) + } + + // simple v1 + "ProcessorCreator2" should "compile ok" in { + implicit val service = new NetService + val openSession = ProcessorCreator[RpcRequestClosure, RpcRequestProcessor, RpcContext, BOpenSessionReq, BOpenSessionResp, NetService]( + (service, _, req) => { + import scala.jdk.CollectionConverters.MapHasAsScala + val username = req.getUsername + val password = req.getPassword + val configurationMap = req.getConfigurationMap + val ret = service.openSession(username, password, configurationMap.asScala.toMap) + BOpenSessionResp.newBuilder().setSessionHandle(ret).build() + }, + (_, _, exception) => { + BOpenSessionResp.newBuilder().setStatus(exception.getLocalizedMessage).build() + } + ) + + println(openSession.defaultResp) + + println(openSession.getClass.getClass.getName) + + println(openSession.interest()) + } + + // simple v2 + "ProcessorCreator3" should "compile ok" in { + // NetService must be a class and with an no parameter construction + val openSession = ProcessorCreator[NetService, RpcRequestClosure, RpcRequestProcessor, RpcContext, BOpenSessionReq, BOpenSessionResp]( + (service, rpc, req) => { + import scala.jdk.CollectionConverters.MapHasAsScala + val username = req.getUsername + val password = req.getPassword + val configurationMap = req.getConfigurationMap + val ret = service.openSession(username, password, configurationMap.asScala.toMap) + BOpenSessionResp.newBuilder().setSessionHandle(ret).build() + }, + (service, rpc, exception) => { + BOpenSessionResp.newBuilder().setStatus(exception.getLocalizedMessage).build() + } + ) + + println(openSession.defaultResp) + + println(openSession.getClass.getClass.getName) + + println(openSession.interest()) + } + + // error + "ProcessorCreator4" should "compile error" in { + trait Service + """ + | val openSession = ProcessorCreator[Service, RpcRequestClosure, RpcRequestProcessor, RpcContext, BOpenSessionReq, BOpenSessionResp]( + | (service, rpc, req) => { + | BOpenSessionResp.newBuilder().setSessionHandle(null).build() + | }, + | (service, rpc, exception) => { + | BOpenSessionResp.newBuilder().setStatus(exception.getLocalizedMessage).build() + | } + | ) + | + | println(openSession.defaultResp) + | + | println(openSession.getClass.getClass.getName) + | + | println(openSession.interest()) + |""".stripMargin shouldNot compile + } + + // error + "ProcessorCreator5" should "compile error" in { + trait Service + """ + | val openSession = ProcessorCreator[Service, RpcRequestProcessor, RpcRequestClosure, RpcContext, BOpenSessionReq, BOpenSessionResp]( + | (service, rpc, req) => { + | BOpenSessionResp.newBuilder().setSessionHandle(null).build() + | }, + | (service, rpc, exception) => { + | BOpenSessionResp.newBuilder().setStatus(exception.getLocalizedMessage).build() + | } + | ) + | + | println(openSession.defaultResp) + | + | println(openSession.getClass.getClass.getName) + | + | println(openSession.interest()) + |""".stripMargin shouldNot compile + } + + // error + "ProcessorCreator6" should "compile error" in { + object Service + """ + | val openSession = ProcessorCreator[Service, RpcRequestClosure, RpcRequestProcessor, RpcContext, BOpenSessionReq, BOpenSessionResp]( + | (service, rpc, req) => { + | BOpenSessionResp.newBuilder().setSessionHandle(null).build() + | }, + | (service, rpc, exception) => { + | BOpenSessionResp.newBuilder().setStatus(exception.getLocalizedMessage).build() + | } + | ) + | + | println(openSession.defaultResp) + | + | println(openSession.getClass.getClass.getName) + | + | println(openSession.interest()) + |""".stripMargin shouldNot compile + } +}