diff --git a/akka-actor-tests/src/test/scala/akka/actor/PropsCreationSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/PropsCreationSpec.scala new file mode 100644 index 0000000000..53d7b33787 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/actor/PropsCreationSpec.scala @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2014 Typesafe Inc. + */ +package akka.actor + +import akka.testkit.AkkaSpec + +object PropsCreationSpec { + + final class A + + final class B + + class OneParamActor(blackhole: A) extends Actor { + override def receive = Actor.emptyBehavior + } + + class TwoParamActor(blackhole1: A, blackhole2: B) extends Actor { + override def receive = Actor.emptyBehavior + } + + object OneParamActorCreator extends akka.japi.Creator[Actor] { + override def create(): Actor = new OneParamActor(null) + } + +} + +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) +class PropsCreationSpec extends AkkaSpec("akka.actor.serialize-creators = on") { + + import akka.actor.PropsCreationSpec._ + + "Props" must { + "work with creator" in { + val p = Props(new OneParamActor(null)) + system.actorOf(p) + } + "work with classOf" in { + val p = Props(classOf[OneParamActor], null) + system.actorOf(p) + } + "work with classOf + 1 `null` param" in { + val p = Props(classOf[OneParamActor], null) + system.actorOf(p) + } + "work with classOf + 2 `null` params" in { + val p = Props(classOf[TwoParamActor], null, null) + system.actorOf(p) + } + } + + "Props Java API" must { + "work with create(creator)" in { + val p = Props.create(OneParamActorCreator) + system.actorOf(p) + } + "work with create(class, param)" in { + val p = Props.create(classOf[OneParamActor], null) + system.actorOf(p) + } + } + +} diff --git a/akka-actor-tests/src/test/scala/akka/util/ReflectSpec.scala b/akka-actor-tests/src/test/scala/akka/util/ReflectSpec.scala new file mode 100644 index 0000000000..d09091bf76 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/util/ReflectSpec.scala @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2014 Typesafe Inc. + */ +package akka.util + +import org.scalatest.{ Matchers, WordSpec } + +import scala.collection.immutable + +object ReflectSpec { + final class A + final class B + + class One(a: A) + class Two(a: A, b: B) + + class MultipleOne(a: A, b: B) { + def this(a: A) { this(a, null) } + def this(b: B) { this(null, b) } + } +} + +class ReflectSpec extends WordSpec with Matchers { + + import akka.util.ReflectSpec._ + + "Reflect#findConstructor" must { + + "deal with simple 1 matching case" in { + Reflect.findConstructor(classOf[One], immutable.Seq(new A)) + } + "deal with 2-params 1 matching case" in { + Reflect.findConstructor(classOf[Two], immutable.Seq(new A, new B)) + } + "deal with 2-params where one is `null` 1 matching case" in { + Reflect.findConstructor(classOf[Two], immutable.Seq(new A, null)) + Reflect.findConstructor(classOf[Two], immutable.Seq(null, new B)) + } + "deal with `null` in 1 matching case" in { + val constructor = Reflect.findConstructor(classOf[One], immutable.Seq(null)) + constructor.newInstance(null) + } + "deal with multiple constructors" in { + Reflect.findConstructor(classOf[MultipleOne], immutable.Seq(new A)) + Reflect.findConstructor(classOf[MultipleOne], immutable.Seq(new B)) + Reflect.findConstructor(classOf[MultipleOne], immutable.Seq(new A, new B)) + } + "throw when multiple matching constructors" in { + intercept[IllegalArgumentException] { + Reflect.findConstructor(classOf[MultipleOne], immutable.Seq(null)) + } + } + } + +} diff --git a/akka-actor/src/main/scala/akka/actor/dungeon/Children.scala b/akka-actor/src/main/scala/akka/actor/dungeon/Children.scala index c608e590b5..2459f65d81 100644 --- a/akka-actor/src/main/scala/akka/actor/dungeon/Children.scala +++ b/akka-actor/src/main/scala/akka/actor/dungeon/Children.scala @@ -188,7 +188,8 @@ private[akka] trait Children { this: ActorCell ⇒ try { val ser = SerializationExtension(cell.system) props.args forall (arg ⇒ - arg.isInstanceOf[NoSerializationVerificationNeeded] || + arg == null || + arg.isInstanceOf[NoSerializationVerificationNeeded] || ser.deserialize(ser.serialize(arg.asInstanceOf[AnyRef]).get, arg.getClass).get != null) } catch { case NonFatal(e) ⇒ throw new IllegalArgumentException(s"pre-creation serialization check failed at [${cell.self.path}/$name]", e) diff --git a/akka-remote-tests/src/multi-jvm/scala/akka/remote/NewRemoteActorSpec.scala b/akka-remote-tests/src/multi-jvm/scala/akka/remote/NewRemoteActorSpec.scala index f8f113265c..9a039763cb 100644 --- a/akka-remote-tests/src/multi-jvm/scala/akka/remote/NewRemoteActorSpec.scala +++ b/akka-remote-tests/src/multi-jvm/scala/akka/remote/NewRemoteActorSpec.scala @@ -3,17 +3,16 @@ */ package akka.remote +import akka.actor.Terminated + import language.postfixOps -import com.typesafe.config.ConfigFactory import akka.actor.Actor import akka.actor.ActorRef import akka.actor.Props -import akka.pattern.ask import testkit.{ STMultiNodeSpec, MultiNodeConfig, MultiNodeSpec } import akka.testkit._ -import akka.actor.Terminated -import scala.concurrent.duration._ import com.typesafe.config.ConfigFactory +import scala.concurrent.duration._ object NewRemoteActorMultiJvmSpec extends MultiNodeConfig { @@ -23,6 +22,12 @@ object NewRemoteActorMultiJvmSpec extends MultiNodeConfig { } } + class SomeActorWithParam(ignored: String) extends Actor { + def receive = { + case "identify" ⇒ sender() ! self + } + } + commonConfig(debugConfig(on = false).withFallback( ConfigFactory.parseString("akka.remote.log-remote-lifecycle-events = off"))) @@ -31,6 +36,7 @@ object NewRemoteActorMultiJvmSpec extends MultiNodeConfig { deployOn(master, """ /service-hello.remote = "@slave@" + /service-hello-null.remote = "@slave@" /service-hello3.remote = "@slave@" """) @@ -65,6 +71,21 @@ class NewRemoteActorSpec extends MultiNodeSpec(NewRemoteActorMultiJvmSpec) enterBarrier("done") } + "be locally instantiated on a remote node (with null parameter) and be able to communicate through its RemoteActorRef" taggedAs LongRunningTest in { + + runOn(master) { + val actor = system.actorOf(Props(classOf[SomeActorWithParam], null), "service-hello-null") + actor.isInstanceOf[RemoteActorRef] should be(true) + actor.path.address should be(node(slave).address) + + val slaveAddress = testConductor.getAddressFor(slave).await + actor ! "identify" + expectMsgType[ActorRef].path.address should be(slaveAddress) + } + + enterBarrier("done") + } + "be locally instantiated on a remote node and be able to communicate through its RemoteActorRef (with deployOnAll)" taggedAs LongRunningTest in { runOn(master) { diff --git a/akka-remote/src/main/scala/akka/remote/serialization/DaemonMsgCreateSerializer.scala b/akka-remote/src/main/scala/akka/remote/serialization/DaemonMsgCreateSerializer.scala index 6812f3a402..5d28d0da70 100644 --- a/akka-remote/src/main/scala/akka/remote/serialization/DaemonMsgCreateSerializer.scala +++ b/akka-remote/src/main/scala/akka/remote/serialization/DaemonMsgCreateSerializer.scala @@ -54,7 +54,7 @@ private[akka] class DaemonMsgCreateSerializer(val system: ExtendedActorSystem) e .setClazz(props.clazz.getName) .setDeploy(deployProto(props.deploy)) props.args map serialize foreach builder.addArgs - props.args map (_.getClass.getName) foreach builder.addClasses + props.args map (a ⇒ if (a == null) "null" else a.getClass.getName) foreach builder.addClasses builder.build } @@ -93,7 +93,7 @@ private[akka] class DaemonMsgCreateSerializer(val system: ExtendedActorSystem) e import scala.collection.JavaConverters._ val clazz = system.dynamicAccess.getClassFor[AnyRef](proto.getProps.getClazz).get val args: Vector[AnyRef] = (proto.getProps.getArgsList.asScala zip proto.getProps.getClassesList.asScala) - .map(p ⇒ deserialize(p._1, system.dynamicAccess.getClassFor[AnyRef](p._2).get))(collection.breakOut) + .map(deserialize)(collection.breakOut) Props(deploy(proto.getProps.getDeploy), clazz, args) } @@ -106,6 +106,10 @@ private[akka] class DaemonMsgCreateSerializer(val system: ExtendedActorSystem) e protected def serialize(any: Any): ByteString = ByteString.copyFrom(serialization.serialize(any.asInstanceOf[AnyRef]).get) + protected def deserialize(p: (ByteString, String)): AnyRef = + if (p._1.isEmpty && p._2 == "null") null + else deserialize(p._1, system.dynamicAccess.getClassFor[AnyRef](p._2).get) + protected def deserialize[T: ClassTag](data: ByteString, clazz: Class[T]): T = { val bytes = data.toByteArray serialization.deserialize(bytes, clazz) match { diff --git a/akka-remote/src/test/scala/akka/remote/serialization/DaemonMsgCreateSerializerSpec.scala b/akka-remote/src/test/scala/akka/remote/serialization/DaemonMsgCreateSerializerSpec.scala index 1d46715efb..dd221d3fed 100644 --- a/akka-remote/src/test/scala/akka/remote/serialization/DaemonMsgCreateSerializerSpec.scala +++ b/akka-remote/src/test/scala/akka/remote/serialization/DaemonMsgCreateSerializerSpec.scala @@ -16,9 +16,11 @@ import scala.concurrent.duration._ object DaemonMsgCreateSerializerSpec { class MyActor extends Actor { - def receive = { - case _ ⇒ - } + def receive = Actor.emptyBehavior + } + + class MyActorWithParam(ignore: String) extends Actor { + def receive = Actor.emptyBehavior } } @@ -45,6 +47,16 @@ class DaemonMsgCreateSerializerSpec extends AkkaSpec { } } + "serialize and de-serialize DaemonMsgCreate with FromClassCreator, with null parameters for Props" in { + verifySerialization { + DaemonMsgCreate( + props = Props(classOf[MyActorWithParam], null), + deploy = Deploy(), + path = "foo", + supervisor = supervisor) + } + } + "serialize and de-serialize DaemonMsgCreate with function creator" in { verifySerialization { DaemonMsgCreate(