From fbb7cb20a18f41d51f024dba44975e833b9ce0be Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 30 Dec 2011 22:00:49 +0100 Subject: [PATCH] Adding Java docs for Serialization, and discovered some flaws with the Java API, that have been fixed --- .../akka/serialization/SerializeSpec.scala | 2 +- .../main/scala/akka/actor/TypedActor.scala | 4 +- .../akka/serialization/Serialization.scala | 6 +- .../scala/akka/serialization/Serializer.scala | 36 +++-- .../serialization/SerializationDocTest.scala | 8 ++ .../SerializationDocTestBase.java | 135 ++++++++++++++++++ akka-docs/java/serialization.rst | 88 +++++++++++- .../docs/extension/SerializationDocSpec.scala | 31 ---- .../serialization/SerializationDocSpec.scala | 11 +- .../serialization/ProtobufSerializer.scala | 2 +- 10 files changed, 266 insertions(+), 57 deletions(-) create mode 100644 akka-docs/java/code/akka/docs/serialization/SerializationDocTest.scala create mode 100644 akka-docs/java/code/akka/docs/serialization/SerializationDocTestBase.java delete mode 100644 akka-docs/scala/code/akka/docs/extension/SerializationDocSpec.scala diff --git a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala index 09d93030fc..51dc26d6c5 100644 --- a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala @@ -17,7 +17,7 @@ import com.google.protobuf.Message class ProtobufSerializer extends Serializer { val ARRAY_OF_BYTE_ARRAY = Array[Class[_]](classOf[Array[Byte]]) def includeManifest: Boolean = true - def identifier = 2: Serializer.Identifier + def identifier = 2 def toBinary(obj: AnyRef): Array[Byte] = { if (!obj.isInstanceOf[Message]) throw new IllegalArgumentException( diff --git a/akka-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-actor/src/main/scala/akka/actor/TypedActor.scala index cbdfa912e1..3dc5d4c000 100644 --- a/akka-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-actor/src/main/scala/akka/actor/TypedActor.scala @@ -193,7 +193,7 @@ object TypedActor extends ExtensionId[TypedActorExtension] with ExtensionIdProvi private def writeReplace(): AnyRef = parameters match { case null ⇒ SerializedMethodCall(method.getDeclaringClass, method.getName, method.getParameterTypes, null, null) - case ps if ps.length == 0 ⇒ SerializedMethodCall(method.getDeclaringClass, method.getName, method.getParameterTypes, Array[Serializer.Identifier](), Array[Array[Byte]]()) + case ps if ps.length == 0 ⇒ SerializedMethodCall(method.getDeclaringClass, method.getName, method.getParameterTypes, Array[Int](), Array[Array[Byte]]()) case ps ⇒ val serializers: Array[Serializer] = ps map SerializationExtension(Serialization.currentSystem.value).findSerializerFor val serializedParameters: Array[Array[Byte]] = Array.ofDim[Array[Byte]](serializers.length) @@ -207,7 +207,7 @@ object TypedActor extends ExtensionId[TypedActorExtension] with ExtensionIdProvi /** * Represents the serialized form of a MethodCall, uses readResolve and writeReplace to marshall the call */ - case class SerializedMethodCall(ownerType: Class[_], methodName: String, parameterTypes: Array[Class[_]], serializerIdentifiers: Array[Serializer.Identifier], serializedParameters: Array[Array[Byte]]) { + case class SerializedMethodCall(ownerType: Class[_], methodName: String, parameterTypes: Array[Class[_]], serializerIdentifiers: Array[Int], serializedParameters: Array[Array[Byte]]) { //TODO implement writeObject and readObject to serialize //TODO Possible optimization is to special encode the parameter-types to conserve space diff --git a/akka-actor/src/main/scala/akka/serialization/Serialization.scala b/akka-actor/src/main/scala/akka/serialization/Serialization.scala index 1be18b10fd..7fe3703150 100644 --- a/akka-actor/src/main/scala/akka/serialization/Serialization.scala +++ b/akka-actor/src/main/scala/akka/serialization/Serialization.scala @@ -73,7 +73,7 @@ class Serialization(val system: ActorSystemImpl) extends Extension { * Returns either the resulting object or an Exception if one was thrown. */ def deserialize(bytes: Array[Byte], - serializerId: Serializer.Identifier, + serializerId: Int, clazz: Option[Class[_]], classLoader: Option[ClassLoader]): Either[Exception, AnyRef] = try { @@ -164,9 +164,9 @@ class Serialization(val system: ActorSystemImpl) extends Extension { lazy val serializerMap: Map[String, Serializer] = bindings mapValues serializers /** - * Maps from a Serializer.Identifier (Byte) to a Serializer instance (optimization) + * Maps from a Serializer Identity (Int) to a Serializer instance (optimization) */ - lazy val serializerByIdentity: Map[Serializer.Identifier, Serializer] = + lazy val serializerByIdentity: Map[Int, Serializer] = Map(NullSerializer.identifier -> NullSerializer) ++ serializers map { case (_, v) ⇒ (v.identifier, v) } } diff --git a/akka-actor/src/main/scala/akka/serialization/Serializer.scala b/akka-actor/src/main/scala/akka/serialization/Serializer.scala index 5e128be769..a39f77b210 100644 --- a/akka-actor/src/main/scala/akka/serialization/Serializer.scala +++ b/akka-actor/src/main/scala/akka/serialization/Serializer.scala @@ -7,10 +7,6 @@ package akka.serialization import java.io.{ ObjectOutputStream, ByteArrayOutputStream, ObjectInputStream, ByteArrayInputStream } import akka.util.ClassLoaderObjectInputStream -object Serializer { - type Identifier = Int -} - /** * A Serializer represents a bimap between an object and an array of bytes representing that object */ @@ -19,7 +15,7 @@ trait Serializer extends scala.Serializable { * Completely unique value to identify this implementation of Serializer, used to optimize network traffic * Values from 0 to 16 is reserved for Akka internal usage */ - def identifier: Serializer.Identifier + def identifier: Int /** * Serializes the given object into an Array of Byte @@ -31,10 +27,34 @@ trait Serializer extends scala.Serializable { */ def includeManifest: Boolean + /** + * Deserializes the given Array of Bytes into an AnyRef + */ + def fromBinary(bytes: Array[Byte]): AnyRef = fromBinary(bytes, None, None) + + /** + * Deserializes the given Array of Bytes into an AnyRef with an optional type hint + */ + def fromBinary(bytes: Array[Byte], manifest: Option[Class[_]]): AnyRef = fromBinary(bytes, manifest, None) + /** * Produces an object from an array of bytes, with an optional type-hint and a classloader to load the class into */ - def fromBinary(bytes: Array[Byte], manifest: Option[Class[_]] = None, classLoader: Option[ClassLoader] = None): AnyRef + def fromBinary(bytes: Array[Byte], manifest: Option[Class[_]], classLoader: Option[ClassLoader]): AnyRef +} + +/** + * Java API for creating a Serializer + */ +abstract class JSerializer extends Serializer { + def fromBinary(bytes: Array[Byte], manifest: Option[Class[_]] = None, classLoader: Option[ClassLoader] = None): AnyRef = + fromBinary(bytes, manifest.orNull, classLoader.orNull) + + /** + * This method should be overridden, + * manifest and classLoader may be null. + */ + def fromBinary(bytes: Array[Byte], manifest: Class[_], classLoader: ClassLoader): AnyRef } object JavaSerializer extends JavaSerializer @@ -47,7 +67,7 @@ class JavaSerializer extends Serializer { def includeManifest: Boolean = false - def identifier = 1: Serializer.Identifier + def identifier = 1 def toBinary(o: AnyRef): Array[Byte] = { val bos = new ByteArrayOutputStream @@ -74,7 +94,7 @@ class JavaSerializer extends Serializer { class NullSerializer extends Serializer { val nullAsBytes = Array[Byte]() def includeManifest: Boolean = false - def identifier = 0: Serializer.Identifier + def identifier = 0 def toBinary(o: AnyRef) = nullAsBytes def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]] = None, classLoader: Option[ClassLoader] = None): AnyRef = null } diff --git a/akka-docs/java/code/akka/docs/serialization/SerializationDocTest.scala b/akka-docs/java/code/akka/docs/serialization/SerializationDocTest.scala new file mode 100644 index 0000000000..538756d48a --- /dev/null +++ b/akka-docs/java/code/akka/docs/serialization/SerializationDocTest.scala @@ -0,0 +1,8 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.docs.serialization + +import org.scalatest.junit.JUnitSuite + +class SerializationDocTest extends SerializationDocTestBase with JUnitSuite diff --git a/akka-docs/java/code/akka/docs/serialization/SerializationDocTestBase.java b/akka-docs/java/code/akka/docs/serialization/SerializationDocTestBase.java new file mode 100644 index 0000000000..176eb3f480 --- /dev/null +++ b/akka-docs/java/code/akka/docs/serialization/SerializationDocTestBase.java @@ -0,0 +1,135 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.docs.serialization; + +import akka.serialization.JSerializer; +import akka.serialization.Serialization; +import akka.serialization.SerializationExtension; +import akka.serialization.Serializer; +import org.junit.Test; +//#imports + +import akka.serialization.*; +import akka.actor.ActorSystem; +import com.typesafe.config.*; + +//#imports + +public class SerializationDocTestBase { + //#my-own-serializer + public static class MyOwnSerializer extends JSerializer { + + // This is whether "fromBinary" requires a "clazz" or not + @Override public boolean includeManifest() { + return false; + } + + // Pick a unique identifier for your Serializer, + // you've got a couple of billions to choose from, + // 0 - 16 is reserved by Akka itself + @Override public int identifier() { + return 1234567; + } + + // "toBinary" serializes the given object to an Array of Bytes + @Override public byte[] toBinary(Object obj) { + // Put the code that serializes the object here + //#... + return new byte[0]; + //#... + } + + // "fromBinary" deserializes the given array, + // using the type hint (if any, see "includeManifest" above) + // into the optionally provided classLoader. + @Override public Object fromBinary(byte[] bytes, + Class clazz, + ClassLoader classLoader) { + // Put your code that deserializes here + //#... + return null; + //#... + } + } +//#my-own-serializer + @Test public void haveExamples() { + /* + //#serialize-messages-config + akka { + actor { + serialize-messages = on + } + } + //#serialize-messages-config + + //#serialize-creators-config + akka { + actor { + serialize-creators = on + } + } + //#serialize-creators-config + + + //#serialize-serializers-config + akka { + actor { + serializers { + default = "akka.serialization.JavaSerializer" + + myown = "akka.docs.serialization.MyOwnSerializer" + } + } + } + //#serialize-serializers-config + + //#serialization-bindings-config + akka { + actor { + serializers { + default = "akka.serialization.JavaSerializer" + java = "akka.serialization.JavaSerializer" + myown = "akka.docs.serialization.MyOwnSerializer" + } + + serialization-bindings { + java = ["java.lang.String", + "app.my.Customer"] + myown = ["my.own.BusinessObject", + "something.equally.Awesome", + "java.lang.Boolean"] + } + } + } + //#serialization-bindings-config + */ + } + + @Test public void demonstrateTheProgrammaticAPI() { + //#programmatic + ActorSystem system = ActorSystem.create("example"); + + // Get the Serialization Extension + Serialization serialization = SerializationExtension.get(system); + + // Have something to serialize + String original = "woohoo"; + + // Find the Serializer for it + Serializer serializer = serialization.findSerializerFor(original); + + // Turn it into bytes + byte[] bytes = serializer.toBinary(original); + + // Turn it back into an object, + // the nulls are for the class manifest and for the classloader + String back = (String)serializer.fromBinary(bytes); + + // Voilá! + assertEquals(original, back); + + //#programmatic + system.shutdown(); + } +} diff --git a/akka-docs/java/serialization.rst b/akka-docs/java/serialization.rst index 3d30f62864..1ead054d75 100644 --- a/akka-docs/java/serialization.rst +++ b/akka-docs/java/serialization.rst @@ -5,8 +5,90 @@ Serialization (Java) ##################### -Serialization will soon be documented. +.. sidebar:: Contents -Until then we refer to the following section in the configuration file: + .. contents:: :local: -* `Serializers `_ \ No newline at end of file +Akka has a built-in Extension for serialization, +and it is both possible to use the built-in serializers and to write your own. + +The serialization mechanism is both used by Akka internally to serialize messages, +and available for ad-hoc serialization of whatever you might need it for. + +Usage +===== + +Configuration +------------- + +For Akka to know which ``Serializer`` to use for what, you need edit your Akka Configuration, +in the "akka.actor.serializers"-section you bind names to implementations of the ``akka.serialization.Serializer`` +you wish to use, like this: + +.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java#serialize-serializers-config + +.. note:: + + The name ``default`` is special in the sense that the ``Serializer`` + mapped to it will be used as default. + +After you've bound names to different implementations of ``Serializer`` you need to wire which classes +should be serialized using which ``Serializer``, this is done in the "akka.actor.serialization-bindings"-section: + +.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java#serialization-bindings-config + +.. note:: + + Akka currently only checks for absolute equality of Classes, i.e. it does not yet check ``isAssignableFrom``, + this means that you'll need to list the specific classes. + +Verification +------------ + +If you want to verify that your messages are serializable you can enable the following config option: + +.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java#serialize-messages-config + +.. warning:: + + We only recommend using the config option turned on when you're running tests. + It is completely pointless to have it turned on in other scenarios. + +If you want to verify that your ``Props`` are serializable you can enable the following config option: + +.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java#serialize-creators-config + +.. warning:: + + We only recommend using the config option turned on when you're running tests. + It is completely pointless to have it turned on in other scenarios. + +Programmatic +------------ + +If you want to programmatically serialize/deserialize using Akka Serialization, +here's some examples: + +.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java + :include: imports,programmatic + +For more information, have a look at the ``ScalaDoc`` for ``akka.serialization._`` + +Customization +============= + +So, lets say that you want to create your own ``Serializer``, +you saw the ``akka.docs.serialization.MyOwnSerializer`` in the config example above? + +Creating new Serializers +------------------------ + +First you need to create a class definition of your ``Serializer``, +which is done by extending ``akka.serialization.JSerializer``, like this: + +.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java + :include: imports,my-own-serializer + :exclude: ... + +Then you only need to fill in the blanks, bind it to a name in your Akka Configuration and then +list which classes that should be serialized using it. \ No newline at end of file diff --git a/akka-docs/scala/code/akka/docs/extension/SerializationDocSpec.scala b/akka-docs/scala/code/akka/docs/extension/SerializationDocSpec.scala deleted file mode 100644 index 07c3d7c335..0000000000 --- a/akka-docs/scala/code/akka/docs/extension/SerializationDocSpec.scala +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (C) 2009-2011 Typesafe Inc. - */ -package akka.docs.extension - -import org.scalatest.WordSpec -import org.scalatest.matchers.MustMatchers - -class SerializationDocSpec extends WordSpec with MustMatchers { - - "demonstrate how to use Serialization" in { - """ - serializers { - # java = "akka.serialization.JavaSerializer" - # proto = "akka.serialization.ProtobufSerializer" - - default = "akka.serialization.JavaSerializer" - } - - # serialization-bindings { - # java = ["akka.serialization.SerializeSpec$Address", - # "akka.serialization.MyJavaSerializableActor", - # "akka.serialization.MyStatelessActorWithMessagesInMailbox", - # "akka.serialization.MyActorWithProtobufMessagesInMailbox"] - # proto = ["com.google.protobuf.Message", - # "akka.actor.ProtobufProtocol$MyMessage"] - # } - """ - } - -} diff --git a/akka-docs/scala/code/akka/docs/serialization/SerializationDocSpec.scala b/akka-docs/scala/code/akka/docs/serialization/SerializationDocSpec.scala index d28f3e1ac1..ea6bdf8c33 100644 --- a/akka-docs/scala/code/akka/docs/serialization/SerializationDocSpec.scala +++ b/akka-docs/scala/code/akka/docs/serialization/SerializationDocSpec.scala @@ -5,17 +5,12 @@ package akka.docs.serialization import org.scalatest.matchers.MustMatchers import akka.testkit._ +//#imports import akka.actor.ActorSystem +import akka.serialization._ import com.typesafe.config.ConfigFactory //#imports -import akka.serialization._ - -//#imports - -object SerializationDocSpec { - -} //#my-own-serializer class MyOwnSerializer extends Serializer { @@ -26,7 +21,7 @@ class MyOwnSerializer extends Serializer { // Pick a unique identifier for your Serializer, // you've got a couple of billions to choose from, // 0 - 16 is reserved by Akka itself - def identifier = 1234567: Serializer.Identifier + def identifier = 1234567 // "toBinary" serializes the given object to an Array of Bytes def toBinary(obj: AnyRef): Array[Byte] = { diff --git a/akka-remote/src/main/scala/akka/serialization/ProtobufSerializer.scala b/akka-remote/src/main/scala/akka/serialization/ProtobufSerializer.scala index bafe73a080..af206a4234 100644 --- a/akka-remote/src/main/scala/akka/serialization/ProtobufSerializer.scala +++ b/akka-remote/src/main/scala/akka/serialization/ProtobufSerializer.scala @@ -12,7 +12,7 @@ import com.google.protobuf.Message class ProtobufSerializer extends Serializer { val ARRAY_OF_BYTE_ARRAY = Array[Class[_]](classOf[Array[Byte]]) def includeManifest: Boolean = true - def identifier = 2: Serializer.Identifier + def identifier = 2 def toBinary(obj: AnyRef): Array[Byte] = obj match { case m: Message ⇒ m.toByteArray