diff --git a/akka-remote/src/main/scala/remote/MessageSerializer.scala b/akka-remote/src/main/scala/remote/MessageSerializer.scala index 49f38524f9..7cda9e4b4c 100644 --- a/akka-remote/src/main/scala/remote/MessageSerializer.scala +++ b/akka-remote/src/main/scala/remote/MessageSerializer.scala @@ -28,17 +28,21 @@ object MessageSerializer extends Logging { messageProtocol.getSerializationScheme match { case SerializationSchemeType.JAVA => unbox(SERIALIZER_JAVA.fromBinary(messageProtocol.getMessage.toByteArray, None)) + case SerializationSchemeType.PROTOBUF => val clazz = loadManifest(SERIALIZER_PROTOBUF.classLoader, messageProtocol) SERIALIZER_PROTOBUF.fromBinary(messageProtocol.getMessage.toByteArray, Some(clazz)) + case SerializationSchemeType.SBINARY => val clazz = loadManifest(SERIALIZER_SBINARY.classLoader, messageProtocol) val renderer = clazz.newInstance.asInstanceOf[Serializable.SBinary[_ <: AnyRef]] renderer.fromBytes(messageProtocol.getMessage.toByteArray) + case SerializationSchemeType.SCALA_JSON => val clazz = loadManifest(SERIALIZER_SCALA_JSON.classLoader, messageProtocol) - import scala.reflect._ - SERIALIZER_SCALA_JSON.fromBinary(messageProtocol.getMessage.toByteArray)(Manifest.classType(clazz)) + val renderer = clazz.newInstance.asInstanceOf[Serializable.ScalaJSON[_]] + renderer.fromBytes(messageProtocol.getMessage.toByteArray) + case SerializationSchemeType.JAVA_JSON => val clazz = loadManifest(SERIALIZER_JAVA_JSON.classLoader, messageProtocol) SERIALIZER_JAVA_JSON.fromBinary(messageProtocol.getMessage.toByteArray, Some(clazz)) @@ -52,9 +56,9 @@ object MessageSerializer extends Logging { builder.setSerializationScheme(SerializationSchemeType.PROTOBUF) builder.setMessage(ByteString.copyFrom(serializable.toByteArray)) builder.setMessageManifest(ByteString.copyFromUtf8(serializable.getClass.getName)) - } else if (message.isInstanceOf[Serializable.ScalaJSON]) { + } else if (message.isInstanceOf[Serializable.ScalaJSON[_]]) { builder.setSerializationScheme(SerializationSchemeType.SCALA_JSON) - setMessageAndManifest(builder, message.asInstanceOf[Serializable.ScalaJSON]) + setMessageAndManifest(builder, message.asInstanceOf[Serializable.ScalaJSON[_ <: Any]]) } else if (message.isInstanceOf[Serializable.SBinary[_]]) { builder.setSerializationScheme(SerializationSchemeType.SBINARY) setMessageAndManifest(builder, message.asInstanceOf[Serializable.SBinary[_ <: Any]]) diff --git a/akka-remote/src/main/scala/serialization/Serializable.scala b/akka-remote/src/main/scala/serialization/Serializable.scala index 216869d7a6..c446dbbe59 100644 --- a/akka-remote/src/main/scala/serialization/Serializable.scala +++ b/akka-remote/src/main/scala/serialization/Serializable.scala @@ -91,10 +91,32 @@ object Serializable { } /** + * case class Address(street: String, city: String, zip: String) + * extends ScalaJSON[Address] { + * + * implicit val AddressFormat: Format[Address] = + * asProduct3("street", "city", "zip")(Address)(Address.unapply(_).get) + * + * import dispatch.json._ + * import sjson.json._ + * import sjson.json.JsonSerialization._ + * + * def toJSON: String = JsValue.toJson(tojson(this)) + * def toBytes: Array[Byte] = tobinary(this) + * def fromBytes(bytes: Array[Byte]): Address = frombinary[Address](bytes) + * def fromJSON(js: String): Address = fromjson[Address](Js(js)) + * } + * + * val a = Address(...) + * val js = tojson(a) + * val add = fromjson[Address](js) + * * @author Jonas Bonér */ - trait ScalaJSON extends JSON { + trait ScalaJSON[T] extends JSON { def toJSON: String = new String(toBytes, "UTF-8") - def toBytes: Array[Byte] = SJSONSerializer.SJSON.out(this) + def fromJSON(js: String): T + def toBytes: Array[Byte] + def fromBytes(bytes: Array[Byte]): T } } diff --git a/akka-remote/src/main/scala/serialization/Serializer.scala b/akka-remote/src/main/scala/serialization/Serializer.scala index 1365a7d4c1..d4950b323e 100644 --- a/akka-remote/src/main/scala/serialization/Serializer.scala +++ b/akka-remote/src/main/scala/serialization/Serializer.scala @@ -128,11 +128,25 @@ object Serializer { /** * @author Jonas Bonér */ - object ScalaJSON extends ScalaJSON - trait ScalaJSON extends Serializer { + trait ScalaJSON { + import dispatch.json._ + import sjson.json._ + import sjson.json.JsonSerialization + + var classLoader: Option[ClassLoader] = None + + def tojson[T](o: T)(implicit tjs: Writes[T]): JsValue = JsonSerialization.tojson(o)(tjs) + + def fromjson[T](json: JsValue)(implicit fjs: Reads[T]): T = JsonSerialization.fromjson(json)(fjs) + + def tobinary[T](o: T)(implicit tjs: Writes[T]): Array[Byte] = JsonSerialization.tobinary(o)(tjs) + + def frombinary[T](bytes: Array[Byte])(implicit fjs: Reads[T]): T = JsonSerialization.frombinary(bytes)(fjs) + + // backward compatibility + // implemented using refelction based json serialization def toBinary(obj: AnyRef): Array[Byte] = SJSONSerializer.SJSON.out(obj) - // FIXME set ClassLoader on SJSONSerializer.SJSON def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef = SJSONSerializer.SJSON.in(bytes) import scala.reflect.Manifest @@ -144,6 +158,7 @@ object Serializer { SJSONSerializer.SJSON.in(bytes)(m) } } + object ScalaJSON extends ScalaJSON /** * @author Jonas Bonér diff --git a/akka-remote/src/test/scala/serialization/ScalaJSONSerializableSpec.scala b/akka-remote/src/test/scala/serialization/ScalaJSONSerializableSpec.scala new file mode 100644 index 0000000000..0ca548d4e1 --- /dev/null +++ b/akka-remote/src/test/scala/serialization/ScalaJSONSerializableSpec.scala @@ -0,0 +1,67 @@ +package se.scalablesolutions.akka.serialization + +import org.scalatest.Spec +import org.scalatest.matchers.ShouldMatchers +import org.scalatest.BeforeAndAfterAll +import org.scalatest.junit.JUnitRunner +import org.junit.runner.RunWith + +import se.scalablesolutions.akka.serialization.Serializable.ScalaJSON + +object Serializables { + import sjson.json.DefaultProtocol._ + case class Shop(store: String, item: String, price: Int) extends + ScalaJSON[Shop] { + implicit val ShopFormat: sjson.json.Format[Shop] = + asProduct3("store", "item", "price")(Shop)(Shop.unapply(_).get) + + import dispatch.json._ + import sjson.json._ + import sjson.json.JsonSerialization._ + + def toBytes: Array[Byte] = tobinary(this) + def fromBytes(bytes: Array[Byte]) = frombinary[Shop](bytes) + def fromJSON(js: String) = fromjson[Shop](Js(js)) + } + + case class MyMessage(val id: String, val value: Tuple2[String, Int]) + implicit val MyMessageFormat: sjson.json.Format[MyMessage] = + asProduct2("id", "value")(MyMessage)(MyMessage.unapply(_).get) + + case class MyJsonObject(val key: String, val map: Map[String, Int], + val standAloneInt: Int) extends ScalaJSON[MyJsonObject] { + implicit val MyJsonObjectFormat: sjson.json.Format[MyJsonObject] = + asProduct3("key", "map", "standAloneInt")(MyJsonObject)(MyJsonObject.unapply(_).get) + + import dispatch.json._ + import sjson.json._ + import sjson.json.JsonSerialization._ + + def toBytes: Array[Byte] = tobinary(this) + def fromBytes(bytes: Array[Byte]) = frombinary[MyJsonObject](bytes) + def fromJSON(js: String) = fromjson[MyJsonObject](Js(js)) + } +} + +@RunWith(classOf[JUnitRunner]) +class ScalaJSONSerializableSpec extends + Spec with + ShouldMatchers with + BeforeAndAfterAll { + + import Serializables._ + describe("Serialization of case classes") { + it("should be able to serialize and de-serialize") { + val s = Shop("Target", "cooker", 120) + s.fromBytes(s.toBytes) should equal(s) + s.fromJSON(s.toJSON) should equal(s) + + val key: String = "myKey" + val value: Int = 123 + val standAloneInt: Int = 35 + val message = MyJsonObject(key, Map(key -> value), standAloneInt) + message.fromBytes(message.toBytes) should equal(message) + message.fromJSON(message.toJSON) should equal(message) + } + } +} diff --git a/akka-remote/src/test/scala/serialization/ScalaJSONSerializerSpec.scala b/akka-remote/src/test/scala/serialization/ScalaJSONSerializerSpec.scala new file mode 100644 index 0000000000..7d1ef4c7a0 --- /dev/null +++ b/akka-remote/src/test/scala/serialization/ScalaJSONSerializerSpec.scala @@ -0,0 +1,52 @@ +package se.scalablesolutions.akka.serialization + +import org.scalatest.Spec +import org.scalatest.matchers.ShouldMatchers +import org.scalatest.BeforeAndAfterAll +import org.scalatest.junit.JUnitRunner +import org.junit.runner.RunWith + +import se.scalablesolutions.akka.serialization.Serializer.ScalaJSON + +object Protocols { + import sjson.json.DefaultProtocol._ + case class Shop(store: String, item: String, price: Int) + implicit val ShopFormat: sjson.json.Format[Shop] = + asProduct3("store", "item", "price")(Shop)(Shop.unapply(_).get) + + case class MyMessage(val id: String, val value: Tuple2[String, Int]) + implicit val MyMessageFormat: sjson.json.Format[MyMessage] = + asProduct2("id", "value")(MyMessage)(MyMessage.unapply(_).get) + + case class MyJsonObject(val key: String, val map: Map[String, Int], + val standAloneInt: Int) + implicit val MyJsonObjectFormat: sjson.json.Format[MyJsonObject] = + asProduct3("key", "map", "standAloneInt")(MyJsonObject)(MyJsonObject.unapply(_).get) +} + +@RunWith(classOf[JUnitRunner]) +class ScalaJSONSerializerSpec extends + Spec with + ShouldMatchers with + BeforeAndAfterAll { + + import Protocols._ + import ScalaJSON._ + describe("Serialization of case classes") { + it("should be able to serialize and de-serialize") { + val s = Shop("Target", "cooker", 120) + fromjson[Shop](tojson(s)) should equal(s) + frombinary[Shop](tobinary(s)) should equal(s) + + val o = MyMessage("dg", ("akka", 100)) + fromjson[MyMessage](tojson(o)) should equal(o) + frombinary[MyMessage](tobinary(o)) should equal(o) + + val key: String = "myKey" + val value: Int = 123 + val standAloneInt: Int = 35 + val message = MyJsonObject(key, Map(key -> value), standAloneInt) + fromjson[MyJsonObject](tojson(message)) should equal(message) + } + } +} diff --git a/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala b/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala index 832a655c22..de64b803fa 100644 --- a/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala +++ b/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala @@ -8,6 +8,7 @@ import org.scalatest.junit.JUnitRunner import org.junit.runner.RunWith import se.scalablesolutions.akka.serialization._ +import dispatch.json._ import se.scalablesolutions.akka.actor._ import ActorSerialization._ import Actor._ @@ -52,6 +53,10 @@ class SerializableTypeClassActorSpec extends implicit object MyStatelessActorFormat extends StatelessActorFormat[MyStatelessActorWithMessagesInMailbox] } + object BinaryFormatMyActorWithSerializableMessages { + implicit object MyActorWithSerializableMessagesFormat extends StatelessActorFormat[MyActorWithSerializableMessages] + } + object BinaryFormatMyJavaSerializableActor { implicit object MyJavaSerializableActorFormat extends SerializerBasedActorFormat[MyJavaSerializableActor] { val serializer = Serializer.Java @@ -139,6 +144,29 @@ class SerializableTypeClassActorSpec extends (actor3 !! "hello-reply").getOrElse("_") should equal("world") } } + + describe("Custom serializable actors") { + it("should serialize and de-serialize") { + import BinaryFormatMyActorWithSerializableMessages._ + + val actor1 = actorOf[MyActorWithSerializableMessages].start + (actor1 ! MyMessage("hello1", ("akka", 100))) + (actor1 ! MyMessage("hello2", ("akka", 200))) + (actor1 ! MyMessage("hello3", ("akka", 300))) + (actor1 ! MyMessage("hello4", ("akka", 400))) + (actor1 ! MyMessage("hello5", ("akka", 500))) + actor1.mailboxSize should be > (0) + val actor2 = fromBinary(toBinary(actor1)) + Thread.sleep(1000) + actor2.mailboxSize should be > (0) + (actor2 !! "hello-reply").getOrElse("_") should equal("world") + + val actor3 = fromBinary(toBinary(actor1, false)) + Thread.sleep(1000) + actor3.mailboxSize should equal(0) + (actor3 !! "hello-reply").getOrElse("_") should equal("world") + } + } } class MyActorWithDualCounter extends Actor { @@ -188,3 +216,27 @@ class MyStatelessActorWithMessagesInMailbox extends Actor { self.reply("world " + count) } } + +class MyActorWithSerializableMessages extends Actor { + def receive = { + case MyMessage(s, t) => + println("# messages in mailbox " + self.mailboxSize) + Thread.sleep(500) + case "hello-reply" => self.reply("world") + } +} + +case class MyMessage(val id: String, val value: Tuple2[String, Int]) + extends Serializable.ScalaJSON[MyMessage] { + + def this() = this(null, null) + import sjson.json.DefaultProtocol._ + import sjson.json._ + import sjson.json.JsonSerialization._ + implicit val MyMessageFormat: sjson.json.Format[MyMessage] = + asProduct2("id", "value")(MyMessage)(MyMessage.unapply(_).get) + + def toBytes: Array[Byte] = tobinary(this) + def fromBytes(bytes: Array[Byte]) = frombinary[MyMessage](bytes) + def fromJSON(js: String) = fromjson[MyMessage](Js(js)) +} diff --git a/akka-remote/src/test/scala/serialization/Ticket436Spec.scala b/akka-remote/src/test/scala/serialization/Ticket436Spec.scala deleted file mode 100644 index 042f3f07be..0000000000 --- a/akka-remote/src/test/scala/serialization/Ticket436Spec.scala +++ /dev/null @@ -1,49 +0,0 @@ -package se.scalablesolutions.akka.actor.serialization - - -import org.scalatest.Spec -import org.scalatest.matchers.ShouldMatchers -import org.scalatest.BeforeAndAfterAll -import org.scalatest.junit.JUnitRunner -import org.junit.runner.RunWith - -import se.scalablesolutions.akka.serialization.Serializer -import se.scalablesolutions.akka.serialization.Serializable.ScalaJSON -import scala.reflect._ -import scala.annotation.target._ -import sjson.json.JSONTypeHint - -@BeanInfo class MyJsonObject(val key: String, - @(JSONTypeHint @field)(value = classOf[Int]) - val map: Map[String, Int], - val standAloneInt: Int) extends ScalaJSON { - private def this() = this(null, null, -1) - override def toString(): String = try { - val mapValue: Int = map.getOrElse(key, -1) - println("Map value: %s".format(mapValue.asInstanceOf[AnyRef].getClass)) - "Key: %s, Map value: %d, Stand Alone Int: %d".format(key, mapValue, standAloneInt) - } catch { - case e: ClassCastException => e.getMessage - case _ => "Unknown error" - } -} - -@RunWith(classOf[JUnitRunner]) -class Ticket436Spec extends - Spec with - ShouldMatchers with - BeforeAndAfterAll { - - describe("Serialization of Maps containing Int") { - it("should be able to serialize and de-serialize preserving the data types of the Map") { - val key: String = "myKey" - val value: Int = 123 - val standAloneInt: Int = 35 - val message = new MyJsonObject(key, Map(key -> value), standAloneInt) - - val json = message.toJSON - val copy = Serializer.ScalaJSON.fromJSON[MyJsonObject](json) - copy.asInstanceOf[MyJsonObject].map.get("myKey").get.isInstanceOf[Int] should equal(true) - } - } -}