From 425dd4846d7b107c46a247cf3d4974cd49b08f07 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Wed, 14 Oct 2015 23:55:56 +0200 Subject: [PATCH 01/11] Experiment with #26 --- .../src/main/scala/stamina/Persister.scala | 21 ++++++++++---- .../src/main/scala/stamina/Persisters.scala | 29 +++++++++++++++---- .../scala/stamina/StaminaAkkaSerializer.scala | 25 ++++++++-------- .../src/main/scala/stamina/stamina.scala | 11 +++++-- .../test/scala/stamina/PersistersSpec.scala | 16 +++++----- .../stamina/StaminaAkkaSerializerSpec.scala | 18 ++++++------ .../scala/stamina/TestOnlyPersister.scala | 2 +- .../src/main/scala/stamina/json/json.scala | 10 ++++--- .../stamina/json/JsonPersisterSpec.scala | 12 ++++---- .../stamina/testkit/StaminaTestKit.scala | 4 +-- .../testkit/ScalatestTestGenerationSpec.scala | 2 +- 11 files changed, 93 insertions(+), 57 deletions(-) diff --git a/stamina-core/src/main/scala/stamina/Persister.scala b/stamina-core/src/main/scala/stamina/Persister.scala index aeee516..bbaee8d 100644 --- a/stamina-core/src/main/scala/stamina/Persister.scala +++ b/stamina-core/src/main/scala/stamina/Persister.scala @@ -11,18 +11,24 @@ import scala.util._ abstract class Persister[T: ClassTag, V <: Version: VersionInfo](val key: String) { lazy val currentVersion = Version.numberFor[V] - def persist(t: T): Persisted + def persist(t: T): Array[Byte] def unpersist(persisted: Persisted): T + def unpersist(manifest: String, persisted: Array[Byte]): T = + // TODO I should really remove that one but this is easier for the PoC + unpersist(Persisted(Manifest.key(manifest), Manifest.version(manifest), persisted)) def canPersist(a: AnyRef): Boolean = convertToT(a).isDefined - def canUnpersist(p: Persisted): Boolean = p.key == key && p.version <= currentVersion + lazy val currentManifest = Manifest.encode(key, currentVersion) + /* To be overridden when a Persister can persist multiple versions */ + def canUnpersist(p: Persisted): Boolean = canUnpersist(Manifest.encode(p.key, p.version)) + def canUnpersist(m: String): Boolean = Manifest.key(m) == key && Manifest.version(m) == currentVersion private[stamina] def convertToT(any: AnyRef): Option[T] = any match { case t: T ⇒ Some(t) case _ ⇒ None } - private[stamina] def persistAny(any: AnyRef): Persisted = { + private[stamina] def persistAny(any: AnyRef): Array[Byte] = { convertToT(any).map(persist(_)).getOrElse( throw new IllegalArgumentException( s"persistAny() was called on Persister[${implicitly[ClassTag[T]].runtimeClass}] with an instance of ${any.getClass}." @@ -30,10 +36,13 @@ abstract class Persister[T: ClassTag, V <: Version: VersionInfo](val key: String ) } - private[stamina] def unpersistAny(persisted: Persisted): AnyRef = { - Try(unpersist(persisted).asInstanceOf[AnyRef]) match { + private[stamina] def unpersistAny(manifest: String, persistedBytes: Array[Byte]): AnyRef = { + Try(unpersist(manifest, persistedBytes).asInstanceOf[AnyRef]) match { case Success(anyref) ⇒ anyref - case Failure(error) ⇒ throw UnrecoverableDataException(persisted, error) + case Failure(error) ⇒ + // TODO simplify + val persisted = Persisted(key, Manifest.version(manifest), ByteString(persistedBytes)) + throw UnrecoverableDataException(persisted, error) } } } diff --git a/stamina-core/src/main/scala/stamina/Persisters.scala b/stamina-core/src/main/scala/stamina/Persisters.scala index 377a5a0..52c640a 100644 --- a/stamina-core/src/main/scala/stamina/Persisters.scala +++ b/stamina-core/src/main/scala/stamina/Persisters.scala @@ -11,19 +11,38 @@ import scala.reflect.ClassTag */ case class Persisters(persisters: List[Persister[_, _]]) { def canPersist(a: AnyRef): Boolean = persisters.exists(_.canPersist(a)) - def canUnpersist(p: Persisted): Boolean = persisters.exists(_.canUnpersist(p)) + def canUnpersist(manifest: String): Boolean = persisters.exists(_.canUnpersist(manifest)) // format: OFF - def persist(anyref: AnyRef): Persisted = { + def manifest(anyref: AnyRef): String = { + persisters.find(_.canPersist(anyref)) + .map(_.currentManifest) + .getOrElse(throw UnregisteredTypeException(anyref)) + } + + def persist(anyref: AnyRef): Array[Byte] = { persisters.find(_.canPersist(anyref)) .map(_.persistAny(anyref)) .getOrElse(throw UnregisteredTypeException(anyref)) } + def unpersist(manifest: String, persisted: Array[Byte]): AnyRef = { + persisters.find(_.canUnpersist(manifest)) + .map(_.unpersistAny(manifest, persisted)) + .getOrElse(throw UnsupportedDataException(Manifest.key(manifest), Manifest.version(manifest))) + } + + def persistAndWrap(anyref: AnyRef): Persisted = { + persisters.find(_.canPersist(anyref)) + .map(p => Persisted(p.key, p.currentVersion, p.persistAny(anyref))) + .getOrElse(throw UnregisteredTypeException(anyref)) + } + def unpersist(persisted: Persisted): AnyRef = { - persisters.find(_.canUnpersist(persisted)) - .map(_.unpersistAny(persisted)) - .getOrElse(throw UnsupportedDataException(persisted)) + val manifest = Manifest.encode(persisted.key, persisted.version) + persisters.find(_.canUnpersist(manifest)) + .map(_.unpersistAny(manifest, persisted.bytes.toArray)) + .getOrElse(throw UnsupportedDataException(Manifest.key(manifest), Manifest.version(manifest))) } // format: ON diff --git a/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala b/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala index 50ad105..ace0b22 100644 --- a/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala +++ b/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala @@ -5,15 +5,15 @@ import akka.serialization._ /** * A custom Akka Serializer specifically designed for use with Akka Persistence. */ -abstract class StaminaAkkaSerializer private[stamina] (persisters: Persisters, codec: PersistedCodec) extends Serializer { - def this(persisters: List[Persister[_, _]], codec: PersistedCodec = DefaultPersistedCodec) = this(Persisters(persisters), codec) - def this(persister: Persister[_, _], persisters: Persister[_, _]*) = this(Persisters(persister :: persisters.toList), DefaultPersistedCodec) +abstract class StaminaAkkaSerializer private[stamina] (persisters: Persisters) extends SerializerWithStringManifest { + def this(persisters: List[Persister[_, _]]) = this(Persisters(persisters)) + def this(persister: Persister[_, _], persisters: Persister[_, _]*) = this(Persisters(persister :: persisters.toList)) - /** We don't need class manifests since we're using keys to identify types. */ - val includeManifest: Boolean = false + /** Uniquely identifies this Serializer. */ + val identifier = 490304 - /** Uniquely identifies this Serializer by combining the codec with a unique number. */ - val identifier = 42 * codec.identifier + def manifest(obj: AnyRef): String = + persisters.manifest(obj) /** * @throws UnregisteredTypeException when the specified object is not supported by the persisters. @@ -21,18 +21,17 @@ abstract class StaminaAkkaSerializer private[stamina] (persisters: Persisters, c def toBinary(obj: AnyRef): Array[Byte] = { if (!persisters.canPersist(obj)) throw UnregisteredTypeException(obj) - codec.writePersisted(persisters.persist(obj)) + persisters.persist(obj) } /** * @throws UnsupportedDataException when the persisted key and/or version is not supported. * @throws UnrecoverableDataException when the key and version are supported but recovery throws an exception. */ - def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef = { - val persisted = codec.readPersisted(bytes) + def fromBinary(bytes: Array[Byte], manifest: String): AnyRef = { + if (manifest.isEmpty) throw new IllegalArgumentException("No manifest found") + if (!persisters.canUnpersist(manifest)) throw UnsupportedDataException(Manifest.key(manifest), Manifest.version(manifest)) - if (!persisters.canUnpersist(persisted)) throw UnsupportedDataException(persisted) - - persisters.unpersist(persisted) + persisters.unpersist(manifest, bytes) } } diff --git a/stamina-core/src/main/scala/stamina/stamina.scala b/stamina-core/src/main/scala/stamina/stamina.scala index bfd0d2d..b14cfaa 100644 --- a/stamina-core/src/main/scala/stamina/stamina.scala +++ b/stamina-core/src/main/scala/stamina/stamina.scala @@ -30,11 +30,18 @@ package stamina { extends RuntimeException(s"No persister registered for class: ${obj.getClass}") with NoStackTrace - case class UnsupportedDataException(persisted: Persisted) - extends RuntimeException(s"No unpersister registered for key: '${persisted.key}' and version: ${persisted.version}") + case class UnsupportedDataException(key: String, version: Int) + extends RuntimeException(s"No unpersister registered for key: '$key' and version: $version") with NoStackTrace case class UnrecoverableDataException(persisted: Persisted, error: Throwable) extends RuntimeException(s"Error while trying to unpersist data with key '${persisted.key}' and version ${persisted.version}. Cause: ${error}") with NoStackTrace + + // TODO this probably needs to change and move, just a PoC + object Manifest { + def encode(key: String, version: Int): String = version + "-" + key + def key(manifest: String): String = manifest.substring(manifest.indexOf('-') + 1) + def version(manifest: String): Int = Integer.valueOf(manifest.substring(0, manifest.indexOf('-'))) + } } diff --git a/stamina-core/src/test/scala/stamina/PersistersSpec.scala b/stamina-core/src/test/scala/stamina/PersistersSpec.scala index 97f6935..d8e4155 100644 --- a/stamina-core/src/test/scala/stamina/PersistersSpec.scala +++ b/stamina-core/src/test/scala/stamina/PersistersSpec.scala @@ -22,20 +22,20 @@ class PersistersSpec extends StaminaSpec { } "correctly implement canUnpersist()" in { - canUnpersist(itemPersister.persist(item1)) should be(true) - canUnpersist(cartPersister.persist(cart)) should be(true) + canUnpersist(itemPersister.currentManifest) should be(true) + canUnpersist(cartPersister.currentManifest) should be(true) - canUnpersist(cartCreatedPersister.persist(cartCreated)) should be(false) - canUnpersist(Persisted("unknown", 1, ByteString("..."))) should be(false) - canUnpersist(Persisted("item", 2, ByteString("..."))) should be(false) + canUnpersist(cartCreatedPersister.currentManifest) should be(false) + canUnpersist(Manifest.encode("unknown", 1)) should be(false) + canUnpersist(Manifest.encode("item", 2)) should be(false) // works because canUnpersist only looks at the key and the version, not at the raw data - canUnpersist(Persisted("item", 1, ByteString("Not an item at all!"))) should be(true) + canUnpersist(Manifest.encode("item", 1)) should be(true) } "correctly implement persist() and unpersist()" in { - unpersist(persist(item1)) should equal(item1) - unpersist(persist(cart)) should equal(cart) + unpersist(persistAndWrap(item1)) should equal(item1) + unpersist(persistAndWrap(cart)) should equal(cart) } "throw an UnregisteredTypeException when persisting an unregistered type" in { diff --git a/stamina-core/src/test/scala/stamina/StaminaAkkaSerializerSpec.scala b/stamina-core/src/test/scala/stamina/StaminaAkkaSerializerSpec.scala index cffa695..1729681 100644 --- a/stamina-core/src/test/scala/stamina/StaminaAkkaSerializerSpec.scala +++ b/stamina-core/src/test/scala/stamina/StaminaAkkaSerializerSpec.scala @@ -10,7 +10,7 @@ class StaminaAkkaSerializerSpec extends StaminaSpec { val cartCreatedPersister = persister[CartCreated]("cart-created") class MyAkkaSerializer1a extends StaminaAkkaSerializer(List(itemPersister, cartPersister, cartCreatedPersister)) - class MyAkkaSerializer1b extends StaminaAkkaSerializer(List(itemPersister, cartPersister, cartCreatedPersister), DefaultPersistedCodec) + class MyAkkaSerializer1b extends StaminaAkkaSerializer(List(itemPersister, cartPersister, cartCreatedPersister)) class MyAkkaSerializer2 extends StaminaAkkaSerializer(itemPersister, cartPersister, cartCreatedPersister) val serializer = new MyAkkaSerializer1a @@ -19,29 +19,29 @@ class StaminaAkkaSerializerSpec extends StaminaSpec { "The StaminaAkkaSerializer" should { "correctly serialize and deserialize the current version of the domain" in { - fromBinary(toBinary(item1)) should equal(item1) - fromBinary(toBinary(item2)) should equal(item2) - fromBinary(toBinary(cart)) should equal(cart) - fromBinary(toBinary(cartCreated)) should equal(cartCreated) + fromBinary(toBinary(item1), manifest(item1)) should equal(item1) + fromBinary(toBinary(item2), manifest(item2)) should equal(item2) + fromBinary(toBinary(cart), manifest(cart)) should equal(cart) + fromBinary(toBinary(cartCreated), manifest(cartCreated)) should equal(cartCreated) } "throw an UnregisteredTypeException when serializing an unregistered type" in { - a[UnregisteredTypeException] should be thrownBy toBinary("a raw String is not supported") + a[UnregisteredTypeException] should be thrownBy toBinary("a raw String is not supported", Manifest.encode("foo", 32)) } "throw an UnsupportedDataException when deserializing data with an unknown key" in { an[UnsupportedDataException] should - be thrownBy fromBinary(writePersisted(Persisted("unknown", 1, ByteString("...")))) + be thrownBy fromBinary(writePersisted(Persisted("unknown", 1, ByteString("..."))), Manifest.encode("unknown", 1)) } "throw an UnsupportedDataException when deserializing data with an unsupported version" in { an[UnsupportedDataException] should - be thrownBy fromBinary(writePersisted(Persisted("item", 2, ByteString("...")))) + be thrownBy fromBinary(writePersisted(Persisted("item", 2, ByteString("..."))), Manifest.encode("item", 2)) } "throw an UnrecoverableDataException when an exception occurs while deserializing" in { an[UnrecoverableDataException] should - be thrownBy fromBinary(writePersisted(Persisted("item", 1, ByteString("not an item")))) + be thrownBy fromBinary(writePersisted(Persisted("item", 1, ByteString("not an item"))), Manifest.encode("item", 1)) } } } diff --git a/stamina-core/src/test/scala/stamina/TestOnlyPersister.scala b/stamina-core/src/test/scala/stamina/TestOnlyPersister.scala index bc6c737..63256c1 100644 --- a/stamina-core/src/test/scala/stamina/TestOnlyPersister.scala +++ b/stamina-core/src/test/scala/stamina/TestOnlyPersister.scala @@ -12,7 +12,7 @@ object TestOnlyPersister { def persister[T <: AnyRef: ClassTag](key: String): Persister[T, V1] = new JavaPersister[T](key) private class JavaPersister[T <: AnyRef: ClassTag](key: String) extends Persister[T, V1](key) { - def persist(t: T): Persisted = Persisted(key, currentVersion, toBinary(t)) + def persist(t: T): Array[Byte] = toBinary(t) def unpersist(p: Persisted): T = { if (canUnpersist(p)) fromBinary(p.bytes.toArray).asInstanceOf[T] else throw new IllegalArgumentException("") diff --git a/stamina-json/src/main/scala/stamina/json/json.scala b/stamina-json/src/main/scala/stamina/json/json.scala index 6a72d44..5aa323b 100644 --- a/stamina-json/src/main/scala/stamina/json/json.scala +++ b/stamina-json/src/main/scala/stamina/json/json.scala @@ -55,7 +55,9 @@ package object json { */ def persister[T: RootJsonFormat: ClassTag, V <: Version: VersionInfo: MigratableVersion](key: String, migrator: JsonMigrator[V]): JsonPersister[T, V] = new VnJsonPersister[T, V](key, migrator) - private[json] def toJsonBytes[T](t: T)(implicit writer: RootJsonWriter[T]): ByteString = ByteString(writer.write(t).compactPrint) + import java.nio.charset.StandardCharsets + val UTF_8: String = StandardCharsets.UTF_8.name() + private[json] def toJsonBytes[T](t: T)(implicit writer: RootJsonWriter[T]): Array[Byte] = writer.write(t).compactPrint.getBytes(UTF_8) private[json] def fromJsonBytes[T](bytes: ByteString)(implicit reader: RootJsonReader[T]): T = reader.read(parseJson(bytes)) private[json] def parseJson(bytes: ByteString): JsValue = JsonParser(ParserInput(bytes.toArray)) } @@ -70,7 +72,7 @@ package json { } private[json] class V1JsonPersister[T: RootJsonFormat: ClassTag](key: String) extends JsonPersister[T, V1](key) { - def persist(t: T): Persisted = Persisted(key, currentVersion, toJsonBytes(t)) + def persist(t: T): Array[Byte] = toJsonBytes(t) def unpersist(p: Persisted): T = { if (canUnpersist(p)) fromJsonBytes[T](p.bytes) else throw new IllegalArgumentException(cannotUnpersist(p)) @@ -78,9 +80,9 @@ package json { } private[json] class VnJsonPersister[T: RootJsonFormat: ClassTag, V <: Version: VersionInfo: MigratableVersion](key: String, migrator: JsonMigrator[V]) extends JsonPersister[T, V](key) { - override def canUnpersist(p: Persisted): Boolean = p.key == key && migrator.canMigrate(p.version) + override def canUnpersist(m: String): Boolean = Manifest.key(m) == key && migrator.canMigrate(Manifest.version(m)) - def persist(t: T): Persisted = Persisted(key, currentVersion, toJsonBytes(t)) + def persist(t: T): Array[Byte] = toJsonBytes(t) def unpersist(p: Persisted): T = { if (canUnpersist(p)) migrator.migrate(parseJson(p.bytes), p.version).convertTo[T] else throw new IllegalArgumentException(cannotUnpersist(p)) diff --git a/stamina-json/src/test/scala/stamina/json/JsonPersisterSpec.scala b/stamina-json/src/test/scala/stamina/json/JsonPersisterSpec.scala index a643018..f62ac84 100644 --- a/stamina-json/src/test/scala/stamina/json/JsonPersisterSpec.scala +++ b/stamina-json/src/test/scala/stamina/json/JsonPersisterSpec.scala @@ -23,19 +23,19 @@ class JsonPersisterSpec extends StaminaJsonSpec { "V1 persisters produced by SprayJsonPersister" should { "correctly persist and unpersist domain events " in { import v1CartCreatedPersister._ - unpersist(persist(v1CartCreated)) should equal(v1CartCreated) + unpersist(currentManifest, persist(v1CartCreated)) should equal(v1CartCreated) } } "V2 persisters with migrators produced by SprayJsonPersister" should { "correctly persist and unpersist domain events " in { import v2CartCreatedPersister._ - unpersist(persist(v2CartCreated)) should equal(v2CartCreated) + unpersist(currentManifest, persist(v2CartCreated)) should equal(v2CartCreated) } "correctly migrate and unpersist V1 domain events" in { val v1Persisted = v1CartCreatedPersister.persist(v1CartCreated) - val v2Unpersisted = v2CartCreatedPersister.unpersist(v1Persisted) + val v2Unpersisted = v2CartCreatedPersister.unpersist(v1CartCreatedPersister.currentManifest, v1Persisted) v2Unpersisted.cart.items.map(_.price).toSet should equal(Set(1000)) } @@ -44,15 +44,15 @@ class JsonPersisterSpec extends StaminaJsonSpec { "V3 persisters with migrators produced by SprayJsonPersister" should { "correctly persist and unpersist domain events " in { import v3CartCreatedPersister._ - unpersist(persist(v3CartCreated)) should equal(v3CartCreated) + unpersist(currentManifest, persist(v3CartCreated)) should equal(v3CartCreated) } "correctly migrate and unpersist V1 domain events" in { val v1Persisted = v1CartCreatedPersister.persist(v1CartCreated) val v2Persisted = v2CartCreatedPersister.persist(v2CartCreated) - val v1Unpersisted = v3CartCreatedPersister.unpersist(v1Persisted) - val v2Unpersisted = v3CartCreatedPersister.unpersist(v2Persisted) + val v1Unpersisted = v3CartCreatedPersister.unpersist(v1CartCreatedPersister.currentManifest, v1Persisted) + val v2Unpersisted = v3CartCreatedPersister.unpersist(v2CartCreatedPersister.currentManifest, v2Persisted) v1Unpersisted.cart.items.map(_.price).toSet should equal(Set(1000)) v2Unpersisted.timestamp should (be > 0L and be < System.currentTimeMillis) diff --git a/stamina-testkit/src/main/scala/stamina/testkit/StaminaTestKit.scala b/stamina-testkit/src/main/scala/stamina/testkit/StaminaTestKit.scala index 7036aec..d5a1f38 100644 --- a/stamina-testkit/src/main/scala/stamina/testkit/StaminaTestKit.scala +++ b/stamina-testkit/src/main/scala/stamina/testkit/StaminaTestKit.scala @@ -26,7 +26,7 @@ trait StaminaTestKit { self: org.scalatest.WordSpecLike ⇒ private def generateRoundtripTestFor(sample: PersistableSample) = { s"persist and unpersist $sample" in { - persisters.unpersist(persisters.persist(sample.persistable)) should equal(sample.persistable) + persisters.unpersist(persisters.persistAndWrap(sample.persistable)) should equal(sample.persistable) } } @@ -42,7 +42,7 @@ trait StaminaTestKit { self: org.scalatest.WordSpecLike ⇒ def latestVersion(persistable: AnyRef) = Try(persisters.persisters.filter(_.canPersist(persistable)).map(_.currentVersion).max).toOption private def verifyByteStringDeserialization(sample: PersistableSample, version: Int, latestVersion: Int): Unit = { - val serialized = persisters.persist(sample.persistable) + val serialized = persisters.persistAndWrap(sample.persistable) byteStringFromResource(serialized.key, version, sample.sampleId) match { case Success(binary) ⇒ persisters.unpersist(binary) should equal(sample.persistable) diff --git a/stamina-testkit/src/test/scala/stamina/testkit/ScalatestTestGenerationSpec.scala b/stamina-testkit/src/test/scala/stamina/testkit/ScalatestTestGenerationSpec.scala index 7996c07..b870a54 100644 --- a/stamina-testkit/src/test/scala/stamina/testkit/ScalatestTestGenerationSpec.scala +++ b/stamina-testkit/src/test/scala/stamina/testkit/ScalatestTestGenerationSpec.scala @@ -12,7 +12,7 @@ class ScalatestTestGenerationSpec extends StaminaTestKitSpec { import TestDomain._ case class ItemPersister(override val key: String) extends Persister[Item, V1](key) { - def persist(t: Item): Persisted = Persisted(key, currentVersion, ByteString()) + def persist(t: Item): Array[Byte] = Array[Byte]() def unpersist(p: Persisted): Item = item1 } From aa6e69d153d50e6652bf4c4ceeb045a77df89dc4 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Thu, 15 Oct 2015 21:47:01 +0200 Subject: [PATCH 02/11] Add Codec-based Serializer (mostly for legacy users) --- .../CodecBasedStaminaAkkaSerializer.scala | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala diff --git a/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala b/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala new file mode 100644 index 0000000..c2a3cf6 --- /dev/null +++ b/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala @@ -0,0 +1,39 @@ +package stamina + +import akka.serialization._ + +/** + * A custom Akka Serializer specifically designed for use with Akka Persistence. + */ +abstract class CodecBasedStaminaAkkaSerializer private[stamina] (persisters: Persisters, codec: PersistedCodec) extends Serializer { + def this(persisters: List[Persister[_, _]], codec: PersistedCodec = DefaultPersistedCodec) = this(Persisters(persisters), codec) + def this(persister: Persister[_, _], persisters: Persister[_, _]*) = this(Persisters(persister :: persisters.toList), DefaultPersistedCodec) + + /** We don't need class manifests since we're using keys to identify types. */ + val includeManifest: Boolean = false + + /** Uniquely identifies this Serializer by combining the codec with a unique number. */ + val identifier = 42 * codec.identifier + + /** + * @throws UnregisteredTypeException when the specified object is not supported by the persisters. + */ + def toBinary(obj: AnyRef): Array[Byte] = { + if (!persisters.canPersist(obj)) throw UnregisteredTypeException(obj) + + codec.writePersisted(persisters.persistAndWrap(obj)) + } + + /** + * @throws UnsupportedDataException when the persisted key and/or version is not supported. + * @throws UnrecoverableDataException when the key and version are supported but recovery throws an exception. + */ + def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef = { + val persisted = codec.readPersisted(bytes) + val manifest = Manifest.encode(persisted.key, persisted.version) + + if (!persisters.canUnpersist(manifest)) throw UnsupportedDataException(persisted.key, persisted.version) + + persisters.unpersist(manifest, persisted.bytes.toArray) + } +} From 666d210750cfc4d8c5f86bbdbe568e80c056ee96 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Thu, 15 Oct 2015 22:04:29 +0200 Subject: [PATCH 03/11] Split Persisted into manifest and bytes in more places --- .../src/main/scala/stamina/Persister.scala | 9 ++------- .../scala/stamina/TestOnlyPersister.scala | 4 ++-- .../src/main/scala/stamina/json/json.scala | 20 +++++++++---------- .../testkit/ScalatestTestGenerationSpec.scala | 2 +- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/stamina-core/src/main/scala/stamina/Persister.scala b/stamina-core/src/main/scala/stamina/Persister.scala index bbaee8d..426ac2b 100644 --- a/stamina-core/src/main/scala/stamina/Persister.scala +++ b/stamina-core/src/main/scala/stamina/Persister.scala @@ -11,16 +11,11 @@ import scala.util._ abstract class Persister[T: ClassTag, V <: Version: VersionInfo](val key: String) { lazy val currentVersion = Version.numberFor[V] + lazy val currentManifest = Manifest.encode(key, currentVersion) def persist(t: T): Array[Byte] - def unpersist(persisted: Persisted): T - def unpersist(manifest: String, persisted: Array[Byte]): T = - // TODO I should really remove that one but this is easier for the PoC - unpersist(Persisted(Manifest.key(manifest), Manifest.version(manifest), persisted)) + def unpersist(manifest: String, persisted: Array[Byte]): T def canPersist(a: AnyRef): Boolean = convertToT(a).isDefined - lazy val currentManifest = Manifest.encode(key, currentVersion) - /* To be overridden when a Persister can persist multiple versions */ - def canUnpersist(p: Persisted): Boolean = canUnpersist(Manifest.encode(p.key, p.version)) def canUnpersist(m: String): Boolean = Manifest.key(m) == key && Manifest.version(m) == currentVersion private[stamina] def convertToT(any: AnyRef): Option[T] = any match { diff --git a/stamina-core/src/test/scala/stamina/TestOnlyPersister.scala b/stamina-core/src/test/scala/stamina/TestOnlyPersister.scala index 63256c1..0523eb6 100644 --- a/stamina-core/src/test/scala/stamina/TestOnlyPersister.scala +++ b/stamina-core/src/test/scala/stamina/TestOnlyPersister.scala @@ -13,8 +13,8 @@ object TestOnlyPersister { private class JavaPersister[T <: AnyRef: ClassTag](key: String) extends Persister[T, V1](key) { def persist(t: T): Array[Byte] = toBinary(t) - def unpersist(p: Persisted): T = { - if (canUnpersist(p)) fromBinary(p.bytes.toArray).asInstanceOf[T] + def unpersist(manifest: String, p: Array[Byte]): T = { + if (canUnpersist(manifest)) fromBinary(p).asInstanceOf[T] else throw new IllegalArgumentException("") } } diff --git a/stamina-json/src/main/scala/stamina/json/json.scala b/stamina-json/src/main/scala/stamina/json/json.scala index 5aa323b..e01bad4 100644 --- a/stamina-json/src/main/scala/stamina/json/json.scala +++ b/stamina-json/src/main/scala/stamina/json/json.scala @@ -58,8 +58,8 @@ package object json { import java.nio.charset.StandardCharsets val UTF_8: String = StandardCharsets.UTF_8.name() private[json] def toJsonBytes[T](t: T)(implicit writer: RootJsonWriter[T]): Array[Byte] = writer.write(t).compactPrint.getBytes(UTF_8) - private[json] def fromJsonBytes[T](bytes: ByteString)(implicit reader: RootJsonReader[T]): T = reader.read(parseJson(bytes)) - private[json] def parseJson(bytes: ByteString): JsValue = JsonParser(ParserInput(bytes.toArray)) + private[json] def fromJsonBytes[T](bytes: Array[Byte])(implicit reader: RootJsonReader[T]): T = reader.read(parseJson(bytes)) + private[json] def parseJson(bytes: Array[Byte]): JsValue = JsonParser(ParserInput(bytes.toArray)) } package json { @@ -67,15 +67,15 @@ package json { * Simple abstract marker superclass to unify (and hide) the two internal Persister implementations. */ sealed abstract class JsonPersister[T: RootJsonFormat: ClassTag, V <: Version: VersionInfo](key: String) extends Persister[T, V](key) { - private[json] def cannotUnpersist(p: Persisted) = - s"""JsonPersister[${implicitly[ClassTag[T]].runtimeClass.getSimpleName}, V${currentVersion}](key = "${key}") cannot unpersist data with key "${p.key}" and version ${p.version}.""" + private[json] def cannotUnpersist(manifest: String) = + s"""JsonPersister[${implicitly[ClassTag[T]].runtimeClass.getSimpleName}, V${currentVersion}](key = "${key}") cannot unpersist data with manifest "$manifest".""" } private[json] class V1JsonPersister[T: RootJsonFormat: ClassTag](key: String) extends JsonPersister[T, V1](key) { def persist(t: T): Array[Byte] = toJsonBytes(t) - def unpersist(p: Persisted): T = { - if (canUnpersist(p)) fromJsonBytes[T](p.bytes) - else throw new IllegalArgumentException(cannotUnpersist(p)) + def unpersist(manifest: String, p: Array[Byte]): T = { + if (canUnpersist(manifest)) fromJsonBytes[T](p) + else throw new IllegalArgumentException(cannotUnpersist(manifest)) } } @@ -83,9 +83,9 @@ package json { override def canUnpersist(m: String): Boolean = Manifest.key(m) == key && migrator.canMigrate(Manifest.version(m)) def persist(t: T): Array[Byte] = toJsonBytes(t) - def unpersist(p: Persisted): T = { - if (canUnpersist(p)) migrator.migrate(parseJson(p.bytes), p.version).convertTo[T] - else throw new IllegalArgumentException(cannotUnpersist(p)) + def unpersist(manifest: String, p: Array[Byte]): T = { + if (canUnpersist(manifest)) migrator.migrate(parseJson(p), Manifest.version(manifest)).convertTo[T] + else throw new IllegalArgumentException(cannotUnpersist(manifest)) } } } diff --git a/stamina-testkit/src/test/scala/stamina/testkit/ScalatestTestGenerationSpec.scala b/stamina-testkit/src/test/scala/stamina/testkit/ScalatestTestGenerationSpec.scala index b870a54..b06583e 100644 --- a/stamina-testkit/src/test/scala/stamina/testkit/ScalatestTestGenerationSpec.scala +++ b/stamina-testkit/src/test/scala/stamina/testkit/ScalatestTestGenerationSpec.scala @@ -13,7 +13,7 @@ class ScalatestTestGenerationSpec extends StaminaTestKitSpec { case class ItemPersister(override val key: String) extends Persister[Item, V1](key) { def persist(t: Item): Array[Byte] = Array[Byte]() - def unpersist(p: Persisted): Item = item1 + def unpersist(manifest: String, p: Array[Byte]): Item = item1 } "A spec generated by StaminaTestKit" should { From 7afde4bd5aaddcab59a04d3d67e0a261fd2764ff Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Thu, 15 Oct 2015 22:59:31 +0200 Subject: [PATCH 04/11] Manifest as a case class --- .../stamina/CodecBasedStaminaAkkaSerializer.scala | 2 +- stamina-core/src/main/scala/stamina/Persister.scala | 10 +++++----- stamina-core/src/main/scala/stamina/Persisters.scala | 12 ++++++------ .../main/scala/stamina/StaminaAkkaSerializer.scala | 7 ++++--- stamina-core/src/main/scala/stamina/stamina.scala | 9 +++++---- .../src/test/scala/stamina/PersistersSpec.scala | 6 +++--- .../scala/stamina/StaminaAkkaSerializerSpec.scala | 9 ++++----- .../src/test/scala/stamina/TestOnlyPersister.scala | 2 +- stamina-json/src/main/scala/stamina/json/json.scala | 10 +++++----- .../testkit/ScalatestTestGenerationSpec.scala | 2 +- 10 files changed, 35 insertions(+), 34 deletions(-) diff --git a/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala b/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala index c2a3cf6..b2fb5ec 100644 --- a/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala +++ b/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala @@ -30,7 +30,7 @@ abstract class CodecBasedStaminaAkkaSerializer private[stamina] (persisters: Per */ def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef = { val persisted = codec.readPersisted(bytes) - val manifest = Manifest.encode(persisted.key, persisted.version) + val manifest = Manifest(persisted.key, persisted.version) if (!persisters.canUnpersist(manifest)) throw UnsupportedDataException(persisted.key, persisted.version) diff --git a/stamina-core/src/main/scala/stamina/Persister.scala b/stamina-core/src/main/scala/stamina/Persister.scala index 426ac2b..5e6bc4a 100644 --- a/stamina-core/src/main/scala/stamina/Persister.scala +++ b/stamina-core/src/main/scala/stamina/Persister.scala @@ -11,12 +11,12 @@ import scala.util._ abstract class Persister[T: ClassTag, V <: Version: VersionInfo](val key: String) { lazy val currentVersion = Version.numberFor[V] - lazy val currentManifest = Manifest.encode(key, currentVersion) + lazy val currentManifest = Manifest(key, currentVersion) def persist(t: T): Array[Byte] - def unpersist(manifest: String, persisted: Array[Byte]): T + def unpersist(manifest: Manifest, persisted: Array[Byte]): T def canPersist(a: AnyRef): Boolean = convertToT(a).isDefined - def canUnpersist(m: String): Boolean = Manifest.key(m) == key && Manifest.version(m) == currentVersion + def canUnpersist(m: Manifest): Boolean = m.key == key && m.version <= currentVersion private[stamina] def convertToT(any: AnyRef): Option[T] = any match { case t: T ⇒ Some(t) @@ -31,12 +31,12 @@ abstract class Persister[T: ClassTag, V <: Version: VersionInfo](val key: String ) } - private[stamina] def unpersistAny(manifest: String, persistedBytes: Array[Byte]): AnyRef = { + private[stamina] def unpersistAny(manifest: Manifest, persistedBytes: Array[Byte]): AnyRef = { Try(unpersist(manifest, persistedBytes).asInstanceOf[AnyRef]) match { case Success(anyref) ⇒ anyref case Failure(error) ⇒ // TODO simplify - val persisted = Persisted(key, Manifest.version(manifest), ByteString(persistedBytes)) + val persisted = Persisted(key, manifest.version, ByteString(persistedBytes)) throw UnrecoverableDataException(persisted, error) } } diff --git a/stamina-core/src/main/scala/stamina/Persisters.scala b/stamina-core/src/main/scala/stamina/Persisters.scala index 52c640a..9a68fca 100644 --- a/stamina-core/src/main/scala/stamina/Persisters.scala +++ b/stamina-core/src/main/scala/stamina/Persisters.scala @@ -11,10 +11,10 @@ import scala.reflect.ClassTag */ case class Persisters(persisters: List[Persister[_, _]]) { def canPersist(a: AnyRef): Boolean = persisters.exists(_.canPersist(a)) - def canUnpersist(manifest: String): Boolean = persisters.exists(_.canUnpersist(manifest)) + def canUnpersist(manifest: Manifest): Boolean = persisters.exists(_.canUnpersist(manifest)) // format: OFF - def manifest(anyref: AnyRef): String = { + def manifest(anyref: AnyRef): Manifest = { persisters.find(_.canPersist(anyref)) .map(_.currentManifest) .getOrElse(throw UnregisteredTypeException(anyref)) @@ -26,10 +26,10 @@ case class Persisters(persisters: List[Persister[_, _]]) { .getOrElse(throw UnregisteredTypeException(anyref)) } - def unpersist(manifest: String, persisted: Array[Byte]): AnyRef = { + def unpersist(manifest: Manifest, persisted: Array[Byte]): AnyRef = { persisters.find(_.canUnpersist(manifest)) .map(_.unpersistAny(manifest, persisted)) - .getOrElse(throw UnsupportedDataException(Manifest.key(manifest), Manifest.version(manifest))) + .getOrElse(throw UnsupportedDataException(manifest.key, manifest.version)) } def persistAndWrap(anyref: AnyRef): Persisted = { @@ -39,10 +39,10 @@ case class Persisters(persisters: List[Persister[_, _]]) { } def unpersist(persisted: Persisted): AnyRef = { - val manifest = Manifest.encode(persisted.key, persisted.version) + val manifest = Manifest(persisted.key, persisted.version) persisters.find(_.canUnpersist(manifest)) .map(_.unpersistAny(manifest, persisted.bytes.toArray)) - .getOrElse(throw UnsupportedDataException(Manifest.key(manifest), Manifest.version(manifest))) + .getOrElse(throw UnsupportedDataException(manifest.key, manifest.version)) } // format: ON diff --git a/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala b/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala index ace0b22..0d75186 100644 --- a/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala +++ b/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala @@ -13,7 +13,7 @@ abstract class StaminaAkkaSerializer private[stamina] (persisters: Persisters) e val identifier = 490304 def manifest(obj: AnyRef): String = - persisters.manifest(obj) + persisters.manifest(obj).manifest /** * @throws UnregisteredTypeException when the specified object is not supported by the persisters. @@ -30,8 +30,9 @@ abstract class StaminaAkkaSerializer private[stamina] (persisters: Persisters) e */ def fromBinary(bytes: Array[Byte], manifest: String): AnyRef = { if (manifest.isEmpty) throw new IllegalArgumentException("No manifest found") - if (!persisters.canUnpersist(manifest)) throw UnsupportedDataException(Manifest.key(manifest), Manifest.version(manifest)) + val m = Manifest(manifest) + if (!persisters.canUnpersist(m)) throw UnsupportedDataException(m.key, m.version) - persisters.unpersist(manifest, bytes) + persisters.unpersist(m, bytes) } } diff --git a/stamina-core/src/main/scala/stamina/stamina.scala b/stamina-core/src/main/scala/stamina/stamina.scala index b14cfaa..ae97bbd 100644 --- a/stamina-core/src/main/scala/stamina/stamina.scala +++ b/stamina-core/src/main/scala/stamina/stamina.scala @@ -38,10 +38,11 @@ package stamina { extends RuntimeException(s"Error while trying to unpersist data with key '${persisted.key}' and version ${persisted.version}. Cause: ${error}") with NoStackTrace - // TODO this probably needs to change and move, just a PoC + case class Manifest(manifest: String) { + lazy val key: String = manifest.substring(manifest.indexOf('-') + 1) + lazy val version: Int = Integer.valueOf(manifest.substring(0, manifest.indexOf('-'))) + } object Manifest { - def encode(key: String, version: Int): String = version + "-" + key - def key(manifest: String): String = manifest.substring(manifest.indexOf('-') + 1) - def version(manifest: String): Int = Integer.valueOf(manifest.substring(0, manifest.indexOf('-'))) + def apply(key: String, version: Int): Manifest = Manifest(version + "-" + key) } } diff --git a/stamina-core/src/test/scala/stamina/PersistersSpec.scala b/stamina-core/src/test/scala/stamina/PersistersSpec.scala index d8e4155..c6eb207 100644 --- a/stamina-core/src/test/scala/stamina/PersistersSpec.scala +++ b/stamina-core/src/test/scala/stamina/PersistersSpec.scala @@ -26,11 +26,11 @@ class PersistersSpec extends StaminaSpec { canUnpersist(cartPersister.currentManifest) should be(true) canUnpersist(cartCreatedPersister.currentManifest) should be(false) - canUnpersist(Manifest.encode("unknown", 1)) should be(false) - canUnpersist(Manifest.encode("item", 2)) should be(false) + canUnpersist(Manifest("unknown", 1)) should be(false) + canUnpersist(Manifest("item", 2)) should be(false) // works because canUnpersist only looks at the key and the version, not at the raw data - canUnpersist(Manifest.encode("item", 1)) should be(true) + canUnpersist(Manifest("item", 1)) should be(true) } "correctly implement persist() and unpersist()" in { diff --git a/stamina-core/src/test/scala/stamina/StaminaAkkaSerializerSpec.scala b/stamina-core/src/test/scala/stamina/StaminaAkkaSerializerSpec.scala index 1729681..65a96e1 100644 --- a/stamina-core/src/test/scala/stamina/StaminaAkkaSerializerSpec.scala +++ b/stamina-core/src/test/scala/stamina/StaminaAkkaSerializerSpec.scala @@ -3,7 +3,6 @@ package stamina class StaminaAkkaSerializerSpec extends StaminaSpec { import TestDomain._ import TestOnlyPersister._ - import DefaultPersistedCodec._ val itemPersister = persister[Item]("item") val cartPersister = persister[Cart]("cart") @@ -26,22 +25,22 @@ class StaminaAkkaSerializerSpec extends StaminaSpec { } "throw an UnregisteredTypeException when serializing an unregistered type" in { - a[UnregisteredTypeException] should be thrownBy toBinary("a raw String is not supported", Manifest.encode("foo", 32)) + a[UnregisteredTypeException] should be thrownBy toBinary("a raw String is not supported", Manifest("foo", 32)) } "throw an UnsupportedDataException when deserializing data with an unknown key" in { an[UnsupportedDataException] should - be thrownBy fromBinary(writePersisted(Persisted("unknown", 1, ByteString("..."))), Manifest.encode("unknown", 1)) + be thrownBy fromBinary(ByteString("...").toArray, Manifest("unknown", 1).manifest) } "throw an UnsupportedDataException when deserializing data with an unsupported version" in { an[UnsupportedDataException] should - be thrownBy fromBinary(writePersisted(Persisted("item", 2, ByteString("..."))), Manifest.encode("item", 2)) + be thrownBy fromBinary(ByteString("...").toArray, Manifest("item", 2).manifest) } "throw an UnrecoverableDataException when an exception occurs while deserializing" in { an[UnrecoverableDataException] should - be thrownBy fromBinary(writePersisted(Persisted("item", 1, ByteString("not an item"))), Manifest.encode("item", 1)) + be thrownBy fromBinary(ByteString("not an item").toArray, Manifest("item", 1).manifest) } } } diff --git a/stamina-core/src/test/scala/stamina/TestOnlyPersister.scala b/stamina-core/src/test/scala/stamina/TestOnlyPersister.scala index 0523eb6..0986eb3 100644 --- a/stamina-core/src/test/scala/stamina/TestOnlyPersister.scala +++ b/stamina-core/src/test/scala/stamina/TestOnlyPersister.scala @@ -13,7 +13,7 @@ object TestOnlyPersister { private class JavaPersister[T <: AnyRef: ClassTag](key: String) extends Persister[T, V1](key) { def persist(t: T): Array[Byte] = toBinary(t) - def unpersist(manifest: String, p: Array[Byte]): T = { + def unpersist(manifest: Manifest, p: Array[Byte]): T = { if (canUnpersist(manifest)) fromBinary(p).asInstanceOf[T] else throw new IllegalArgumentException("") } diff --git a/stamina-json/src/main/scala/stamina/json/json.scala b/stamina-json/src/main/scala/stamina/json/json.scala index e01bad4..b99c98a 100644 --- a/stamina-json/src/main/scala/stamina/json/json.scala +++ b/stamina-json/src/main/scala/stamina/json/json.scala @@ -67,24 +67,24 @@ package json { * Simple abstract marker superclass to unify (and hide) the two internal Persister implementations. */ sealed abstract class JsonPersister[T: RootJsonFormat: ClassTag, V <: Version: VersionInfo](key: String) extends Persister[T, V](key) { - private[json] def cannotUnpersist(manifest: String) = + private[json] def cannotUnpersist(manifest: Manifest) = s"""JsonPersister[${implicitly[ClassTag[T]].runtimeClass.getSimpleName}, V${currentVersion}](key = "${key}") cannot unpersist data with manifest "$manifest".""" } private[json] class V1JsonPersister[T: RootJsonFormat: ClassTag](key: String) extends JsonPersister[T, V1](key) { def persist(t: T): Array[Byte] = toJsonBytes(t) - def unpersist(manifest: String, p: Array[Byte]): T = { + def unpersist(manifest: Manifest, p: Array[Byte]): T = { if (canUnpersist(manifest)) fromJsonBytes[T](p) else throw new IllegalArgumentException(cannotUnpersist(manifest)) } } private[json] class VnJsonPersister[T: RootJsonFormat: ClassTag, V <: Version: VersionInfo: MigratableVersion](key: String, migrator: JsonMigrator[V]) extends JsonPersister[T, V](key) { - override def canUnpersist(m: String): Boolean = Manifest.key(m) == key && migrator.canMigrate(Manifest.version(m)) + override def canUnpersist(m: Manifest): Boolean = m.key == key && migrator.canMigrate(m.version) def persist(t: T): Array[Byte] = toJsonBytes(t) - def unpersist(manifest: String, p: Array[Byte]): T = { - if (canUnpersist(manifest)) migrator.migrate(parseJson(p), Manifest.version(manifest)).convertTo[T] + def unpersist(manifest: Manifest, p: Array[Byte]): T = { + if (canUnpersist(manifest)) migrator.migrate(parseJson(p), manifest.version).convertTo[T] else throw new IllegalArgumentException(cannotUnpersist(manifest)) } } diff --git a/stamina-testkit/src/test/scala/stamina/testkit/ScalatestTestGenerationSpec.scala b/stamina-testkit/src/test/scala/stamina/testkit/ScalatestTestGenerationSpec.scala index b06583e..5905e35 100644 --- a/stamina-testkit/src/test/scala/stamina/testkit/ScalatestTestGenerationSpec.scala +++ b/stamina-testkit/src/test/scala/stamina/testkit/ScalatestTestGenerationSpec.scala @@ -13,7 +13,7 @@ class ScalatestTestGenerationSpec extends StaminaTestKitSpec { case class ItemPersister(override val key: String) extends Persister[Item, V1](key) { def persist(t: Item): Array[Byte] = Array[Byte]() - def unpersist(manifest: String, p: Array[Byte]): Item = item1 + def unpersist(manifest: Manifest, p: Array[Byte]): Item = item1 } "A spec generated by StaminaTestKit" should { From a0180753b4db914a0a3a1be4f6efcd669d6176b5 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Thu, 15 Oct 2015 23:40:52 +0200 Subject: [PATCH 05/11] Made method naming a bit more consistent (hopefully) --- .../stamina/CodecBasedStaminaAkkaSerializer.scala | 2 +- .../src/main/scala/stamina/Persisters.scala | 14 +++++++------- .../main/scala/stamina/StaminaAkkaSerializer.scala | 2 +- .../src/test/scala/stamina/PersistersSpec.scala | 4 ++-- .../scala/stamina/testkit/StaminaTestKit.scala | 10 ++++------ 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala b/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala index b2fb5ec..af31221 100644 --- a/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala +++ b/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala @@ -21,7 +21,7 @@ abstract class CodecBasedStaminaAkkaSerializer private[stamina] (persisters: Per def toBinary(obj: AnyRef): Array[Byte] = { if (!persisters.canPersist(obj)) throw UnregisteredTypeException(obj) - codec.writePersisted(persisters.persistAndWrap(obj)) + codec.writePersisted(persisters.persist(obj)) } /** diff --git a/stamina-core/src/main/scala/stamina/Persisters.scala b/stamina-core/src/main/scala/stamina/Persisters.scala index 9a68fca..d836cec 100644 --- a/stamina-core/src/main/scala/stamina/Persisters.scala +++ b/stamina-core/src/main/scala/stamina/Persisters.scala @@ -20,24 +20,24 @@ case class Persisters(persisters: List[Persister[_, _]]) { .getOrElse(throw UnregisteredTypeException(anyref)) } - def persist(anyref: AnyRef): Array[Byte] = { + def bytes(anyref: AnyRef): Array[Byte] = { persisters.find(_.canPersist(anyref)) .map(_.persistAny(anyref)) .getOrElse(throw UnregisteredTypeException(anyref)) } + def persist(anyref: AnyRef): Persisted = { + persisters.find(_.canPersist(anyref)) + .map(p => Persisted(p.key, p.currentVersion, p.persistAny(anyref))) + .getOrElse(throw UnregisteredTypeException(anyref)) + } + def unpersist(manifest: Manifest, persisted: Array[Byte]): AnyRef = { persisters.find(_.canUnpersist(manifest)) .map(_.unpersistAny(manifest, persisted)) .getOrElse(throw UnsupportedDataException(manifest.key, manifest.version)) } - def persistAndWrap(anyref: AnyRef): Persisted = { - persisters.find(_.canPersist(anyref)) - .map(p => Persisted(p.key, p.currentVersion, p.persistAny(anyref))) - .getOrElse(throw UnregisteredTypeException(anyref)) - } - def unpersist(persisted: Persisted): AnyRef = { val manifest = Manifest(persisted.key, persisted.version) persisters.find(_.canUnpersist(manifest)) diff --git a/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala b/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala index 0d75186..4d8da96 100644 --- a/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala +++ b/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala @@ -21,7 +21,7 @@ abstract class StaminaAkkaSerializer private[stamina] (persisters: Persisters) e def toBinary(obj: AnyRef): Array[Byte] = { if (!persisters.canPersist(obj)) throw UnregisteredTypeException(obj) - persisters.persist(obj) + persisters.bytes(obj) } /** diff --git a/stamina-core/src/test/scala/stamina/PersistersSpec.scala b/stamina-core/src/test/scala/stamina/PersistersSpec.scala index c6eb207..d978900 100644 --- a/stamina-core/src/test/scala/stamina/PersistersSpec.scala +++ b/stamina-core/src/test/scala/stamina/PersistersSpec.scala @@ -34,8 +34,8 @@ class PersistersSpec extends StaminaSpec { } "correctly implement persist() and unpersist()" in { - unpersist(persistAndWrap(item1)) should equal(item1) - unpersist(persistAndWrap(cart)) should equal(cart) + unpersist(persist(item1)) should equal(item1) + unpersist(persist(cart)) should equal(cart) } "throw an UnregisteredTypeException when persisting an unregistered type" in { diff --git a/stamina-testkit/src/main/scala/stamina/testkit/StaminaTestKit.scala b/stamina-testkit/src/main/scala/stamina/testkit/StaminaTestKit.scala index d5a1f38..33c46be 100644 --- a/stamina-testkit/src/main/scala/stamina/testkit/StaminaTestKit.scala +++ b/stamina-testkit/src/main/scala/stamina/testkit/StaminaTestKit.scala @@ -26,7 +26,7 @@ trait StaminaTestKit { self: org.scalatest.WordSpecLike ⇒ private def generateRoundtripTestFor(sample: PersistableSample) = { s"persist and unpersist $sample" in { - persisters.unpersist(persisters.persistAndWrap(sample.persistable)) should equal(sample.persistable) + persisters.unpersist(persisters.persist(sample.persistable)) should equal(sample.persistable) } } @@ -42,10 +42,10 @@ trait StaminaTestKit { self: org.scalatest.WordSpecLike ⇒ def latestVersion(persistable: AnyRef) = Try(persisters.persisters.filter(_.canPersist(persistable)).map(_.currentVersion).max).toOption private def verifyByteStringDeserialization(sample: PersistableSample, version: Int, latestVersion: Int): Unit = { - val serialized = persisters.persistAndWrap(sample.persistable) + val serialized = persisters.persist(sample.persistable) byteStringFromResource(serialized.key, version, sample.sampleId) match { case Success(binary) ⇒ - persisters.unpersist(binary) should equal(sample.persistable) + persisters.unpersist(Manifest(serialized.key, version), binary) should equal(sample.persistable) case Failure(_: java.io.FileNotFoundException) if version == latestVersion ⇒ val writtenToPath = saveByteArrayToTargetSerializationDirectory(serialized.bytes.toArray, serialized.key, version, sample.sampleId) fail(s"You appear to have added a new serialization sample to the stamina persisters' test.\n" + @@ -67,7 +67,7 @@ trait StaminaTestKit { self: org.scalatest.WordSpecLike ⇒ } } - private def byteStringFromResource(key: String, version: Int, sampleId: String): Try[Persisted] = { + private def byteStringFromResource(key: String, version: Int, sampleId: String): Try[Array[Byte]] = { import scala.io.Source val resourceName = s"/$serializedObjectsPackage/${filename(key, version, sampleId)}" @@ -75,8 +75,6 @@ trait StaminaTestKit { self: org.scalatest.WordSpecLike ⇒ .map(Success(_)).getOrElse(Failure(new java.io.FileNotFoundException(resourceName))) .map(Source.fromInputStream(_).mkString) .flatMap(base64.Decode(_)) - .map(akka.util.ByteString(_)) - .map(Persisted(key, version, _)) } private def saveByteArrayToTargetSerializationDirectory(bytes: Array[Byte], key: String, version: Int, sampleId: String) = { From d95a6e9228f890b87db6a6e6c5f794def75627eb Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Mon, 19 Oct 2015 20:52:06 +0200 Subject: [PATCH 06/11] Clarify scaladoc --- .../scala/stamina/CodecBasedStaminaAkkaSerializer.scala | 7 ++++++- .../src/main/scala/stamina/StaminaAkkaSerializer.scala | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala b/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala index af31221..ac7dfc7 100644 --- a/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala +++ b/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala @@ -3,7 +3,12 @@ package stamina import akka.serialization._ /** - * A custom Akka Serializer specifically designed for use with Akka Persistence. + * A custom Akka Serializer encoding key and version along with the serialized object. + * + * This is particularly useful when there is no separate field for metadata, such as when + * dealing with pre-akka-2.3 persistence. + * + * Wrapping/unwrapping the metadata around the serialized object is done by the Codec. */ abstract class CodecBasedStaminaAkkaSerializer private[stamina] (persisters: Persisters, codec: PersistedCodec) extends Serializer { def this(persisters: List[Persister[_, _]], codec: PersistedCodec = DefaultPersistedCodec) = this(Persisters(persisters), codec) diff --git a/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala b/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala index 4d8da96..1d43d34 100644 --- a/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala +++ b/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala @@ -4,6 +4,8 @@ import akka.serialization._ /** * A custom Akka Serializer specifically designed for use with Akka Persistence. + * + * Key and version information is encoded in the manifest. */ abstract class StaminaAkkaSerializer private[stamina] (persisters: Persisters) extends SerializerWithStringManifest { def this(persisters: List[Persister[_, _]]) = this(Persisters(persisters)) From f2bcf1abb778f327814da05387239c0d221c909b Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Mon, 19 Oct 2015 20:57:18 +0200 Subject: [PATCH 07/11] Remove intermediate Persisted when throwing UnrecoverableDataException --- stamina-core/src/main/scala/stamina/Persister.scala | 5 +---- stamina-core/src/main/scala/stamina/stamina.scala | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/stamina-core/src/main/scala/stamina/Persister.scala b/stamina-core/src/main/scala/stamina/Persister.scala index 5e6bc4a..170be15 100644 --- a/stamina-core/src/main/scala/stamina/Persister.scala +++ b/stamina-core/src/main/scala/stamina/Persister.scala @@ -34,10 +34,7 @@ abstract class Persister[T: ClassTag, V <: Version: VersionInfo](val key: String private[stamina] def unpersistAny(manifest: Manifest, persistedBytes: Array[Byte]): AnyRef = { Try(unpersist(manifest, persistedBytes).asInstanceOf[AnyRef]) match { case Success(anyref) ⇒ anyref - case Failure(error) ⇒ - // TODO simplify - val persisted = Persisted(key, manifest.version, ByteString(persistedBytes)) - throw UnrecoverableDataException(persisted, error) + case Failure(error) ⇒ throw UnrecoverableDataException(manifest, error) } } } diff --git a/stamina-core/src/main/scala/stamina/stamina.scala b/stamina-core/src/main/scala/stamina/stamina.scala index ae97bbd..ba02abc 100644 --- a/stamina-core/src/main/scala/stamina/stamina.scala +++ b/stamina-core/src/main/scala/stamina/stamina.scala @@ -34,8 +34,8 @@ package stamina { extends RuntimeException(s"No unpersister registered for key: '$key' and version: $version") with NoStackTrace - case class UnrecoverableDataException(persisted: Persisted, error: Throwable) - extends RuntimeException(s"Error while trying to unpersist data with key '${persisted.key}' and version ${persisted.version}. Cause: ${error}") + case class UnrecoverableDataException(manifest: Manifest, error: Throwable) + extends RuntimeException(s"Error while trying to unpersist data with key '${manifest.key}' and version ${manifest.version}. Cause: ${error}") with NoStackTrace case class Manifest(manifest: String) { From fc806e5769521a16e5f9fe5a07fa556a5784e6b9 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Sat, 24 Oct 2015 22:43:44 +0200 Subject: [PATCH 08/11] Make Persisters API a bit more balanced --- .../CodecBasedStaminaAkkaSerializer.scala | 5 ++-- .../src/main/scala/stamina/Persisted.scala | 7 +++-- .../main/scala/stamina/PersistedCodec.scala | 2 +- .../src/main/scala/stamina/Persisters.scala | 28 ++++++------------- .../scala/stamina/StaminaAkkaSerializer.scala | 4 +-- 5 files changed, 19 insertions(+), 27 deletions(-) diff --git a/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala b/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala index ac7dfc7..a70ec37 100644 --- a/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala +++ b/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala @@ -35,10 +35,9 @@ abstract class CodecBasedStaminaAkkaSerializer private[stamina] (persisters: Per */ def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef = { val persisted = codec.readPersisted(bytes) - val manifest = Manifest(persisted.key, persisted.version) - if (!persisters.canUnpersist(manifest)) throw UnsupportedDataException(persisted.key, persisted.version) + if (!persisters.canUnpersist(persisted.manifest)) throw UnsupportedDataException(persisted.key, persisted.version) - persisters.unpersist(manifest, persisted.bytes.toArray) + persisters.unpersist(persisted) } } diff --git a/stamina-core/src/main/scala/stamina/Persisted.scala b/stamina-core/src/main/scala/stamina/Persisted.scala index 5eac7a7..05a6593 100644 --- a/stamina-core/src/main/scala/stamina/Persisted.scala +++ b/stamina-core/src/main/scala/stamina/Persisted.scala @@ -4,8 +4,11 @@ package stamina * A simple container holding a persistence key, a version number, * and the associated serialized bytes. */ -case class Persisted(key: String, version: Int, bytes: ByteString) +case class Persisted(key: String, version: Int, bytes: Array[Byte]) { + lazy val manifest = Manifest(key, version) +} object Persisted { - def apply(key: String, version: Int, bytes: Array[Byte]): Persisted = apply(key, version, ByteString(bytes)) + def apply(manifest: Manifest, bytes: Array[Byte]): Persisted = apply(manifest.key, manifest.version, bytes) + def apply(key: String, version: Int, bytes: ByteString): Persisted = apply(key, version, bytes.toArray) } diff --git a/stamina-core/src/main/scala/stamina/PersistedCodec.scala b/stamina-core/src/main/scala/stamina/PersistedCodec.scala index 7ec3ffe..958bde2 100644 --- a/stamina-core/src/main/scala/stamina/PersistedCodec.scala +++ b/stamina-core/src/main/scala/stamina/PersistedCodec.scala @@ -33,7 +33,7 @@ object DefaultPersistedCodec extends PersistedCodec { putInt(keyBytes.length). putBytes(keyBytes). putInt(persisted.version). - append(persisted.bytes). + append(ByteString(persisted.bytes)). result. toArray } diff --git a/stamina-core/src/main/scala/stamina/Persisters.scala b/stamina-core/src/main/scala/stamina/Persisters.scala index d836cec..3ace540 100644 --- a/stamina-core/src/main/scala/stamina/Persisters.scala +++ b/stamina-core/src/main/scala/stamina/Persisters.scala @@ -14,28 +14,18 @@ case class Persisters(persisters: List[Persister[_, _]]) { def canUnpersist(manifest: Manifest): Boolean = persisters.exists(_.canUnpersist(manifest)) // format: OFF - def manifest(anyref: AnyRef): Manifest = { - persisters.find(_.canPersist(anyref)) - .map(_.currentManifest) - .getOrElse(throw UnregisteredTypeException(anyref)) - } + private def persister[T <: AnyRef](anyref: T): Persister[T, _] = + persisters + .find(_.canPersist(anyref)) + .map(_.asInstanceOf[Persister[T, _]]) + .getOrElse(throw UnregisteredTypeException(anyref)) - def bytes(anyref: AnyRef): Array[Byte] = { - persisters.find(_.canPersist(anyref)) - .map(_.persistAny(anyref)) - .getOrElse(throw UnregisteredTypeException(anyref)) - } + def manifest(anyref: AnyRef): Manifest = + persister(anyref).currentManifest def persist(anyref: AnyRef): Persisted = { - persisters.find(_.canPersist(anyref)) - .map(p => Persisted(p.key, p.currentVersion, p.persistAny(anyref))) - .getOrElse(throw UnregisteredTypeException(anyref)) - } - - def unpersist(manifest: Manifest, persisted: Array[Byte]): AnyRef = { - persisters.find(_.canUnpersist(manifest)) - .map(_.unpersistAny(manifest, persisted)) - .getOrElse(throw UnsupportedDataException(manifest.key, manifest.version)) + val p = persister(anyref) + Persisted(p.currentManifest, p.persistAny(anyref)) } def unpersist(persisted: Persisted): AnyRef = { diff --git a/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala b/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala index 1d43d34..6d9abf3 100644 --- a/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala +++ b/stamina-core/src/main/scala/stamina/StaminaAkkaSerializer.scala @@ -23,7 +23,7 @@ abstract class StaminaAkkaSerializer private[stamina] (persisters: Persisters) e def toBinary(obj: AnyRef): Array[Byte] = { if (!persisters.canPersist(obj)) throw UnregisteredTypeException(obj) - persisters.bytes(obj) + persisters.persist(obj).bytes } /** @@ -35,6 +35,6 @@ abstract class StaminaAkkaSerializer private[stamina] (persisters: Persisters) e val m = Manifest(manifest) if (!persisters.canUnpersist(m)) throw UnsupportedDataException(m.key, m.version) - persisters.unpersist(m, bytes) + persisters.unpersist(Persisted(m, bytes)) } } From e24b7ccaab1c5bb167400e2b9fecf87f872718e2 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Sun, 25 Oct 2015 10:19:31 +0100 Subject: [PATCH 09/11] Fix compilation error --- .../src/main/scala/stamina/testkit/StaminaTestKit.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stamina-testkit/src/main/scala/stamina/testkit/StaminaTestKit.scala b/stamina-testkit/src/main/scala/stamina/testkit/StaminaTestKit.scala index 33c46be..03ad14f 100644 --- a/stamina-testkit/src/main/scala/stamina/testkit/StaminaTestKit.scala +++ b/stamina-testkit/src/main/scala/stamina/testkit/StaminaTestKit.scala @@ -45,7 +45,7 @@ trait StaminaTestKit { self: org.scalatest.WordSpecLike ⇒ val serialized = persisters.persist(sample.persistable) byteStringFromResource(serialized.key, version, sample.sampleId) match { case Success(binary) ⇒ - persisters.unpersist(Manifest(serialized.key, version), binary) should equal(sample.persistable) + persisters.unpersist(Persisted(Manifest(serialized.key, version), binary)) should equal(sample.persistable) case Failure(_: java.io.FileNotFoundException) if version == latestVersion ⇒ val writtenToPath = saveByteArrayToTargetSerializationDirectory(serialized.bytes.toArray, serialized.key, version, sample.sampleId) fail(s"You appear to have added a new serialization sample to the stamina persisters' test.\n" + From 6ab919f025abf5ce62c4103828ebeca997c2b694 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Sun, 25 Oct 2015 12:18:47 +0100 Subject: [PATCH 10/11] Move codec-related code to its own package As it's not needed for the common use case going forward --- stamina-core/src/main/scala/stamina/Persisters.scala | 6 +++--- .../{ => codec}/CodecBasedStaminaAkkaSerializer.scala | 1 + .../main/scala/stamina/{ => codec}/PersistedCodec.scala | 1 + stamina-core/src/test/scala/stamina/PersistersSpec.scala | 6 +++--- .../test/scala/stamina/StaminaAkkaSerializerSpec.scala | 8 ++++---- 5 files changed, 12 insertions(+), 10 deletions(-) rename stamina-core/src/main/scala/stamina/{ => codec}/CodecBasedStaminaAkkaSerializer.scala (99%) rename stamina-core/src/main/scala/stamina/{ => codec}/PersistedCodec.scala (98%) diff --git a/stamina-core/src/main/scala/stamina/Persisters.scala b/stamina-core/src/main/scala/stamina/Persisters.scala index 3ace540..af75ab4 100644 --- a/stamina-core/src/main/scala/stamina/Persisters.scala +++ b/stamina-core/src/main/scala/stamina/Persisters.scala @@ -28,10 +28,10 @@ case class Persisters(persisters: List[Persister[_, _]]) { Persisted(p.currentManifest, p.persistAny(anyref)) } - def unpersist(persisted: Persisted): AnyRef = { - val manifest = Manifest(persisted.key, persisted.version) + def unpersist(persisted: Persisted): AnyRef = unpersist(persisted.bytes, persisted.manifest) + def unpersist(payload: Array[Byte], manifest: Manifest): AnyRef = { persisters.find(_.canUnpersist(manifest)) - .map(_.unpersistAny(manifest, persisted.bytes.toArray)) + .map(_.unpersistAny(manifest, payload)) .getOrElse(throw UnsupportedDataException(manifest.key, manifest.version)) } // format: ON diff --git a/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala b/stamina-core/src/main/scala/stamina/codec/CodecBasedStaminaAkkaSerializer.scala similarity index 99% rename from stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala rename to stamina-core/src/main/scala/stamina/codec/CodecBasedStaminaAkkaSerializer.scala index a70ec37..870875d 100644 --- a/stamina-core/src/main/scala/stamina/CodecBasedStaminaAkkaSerializer.scala +++ b/stamina-core/src/main/scala/stamina/codec/CodecBasedStaminaAkkaSerializer.scala @@ -1,4 +1,5 @@ package stamina +package codec import akka.serialization._ diff --git a/stamina-core/src/main/scala/stamina/PersistedCodec.scala b/stamina-core/src/main/scala/stamina/codec/PersistedCodec.scala similarity index 98% rename from stamina-core/src/main/scala/stamina/PersistedCodec.scala rename to stamina-core/src/main/scala/stamina/codec/PersistedCodec.scala index 958bde2..b48cac5 100644 --- a/stamina-core/src/main/scala/stamina/PersistedCodec.scala +++ b/stamina-core/src/main/scala/stamina/codec/PersistedCodec.scala @@ -1,4 +1,5 @@ package stamina +package codec /** * The encoding used to translate an instance of Persisted diff --git a/stamina-core/src/test/scala/stamina/PersistersSpec.scala b/stamina-core/src/test/scala/stamina/PersistersSpec.scala index d978900..8f3e32e 100644 --- a/stamina-core/src/test/scala/stamina/PersistersSpec.scala +++ b/stamina-core/src/test/scala/stamina/PersistersSpec.scala @@ -44,17 +44,17 @@ class PersistersSpec extends StaminaSpec { "throw an UnsupportedDataException when unpersisting data with an unknown key" in { an[UnsupportedDataException] should - be thrownBy unpersist(Persisted("unknown", 1, ByteString("..."))) + be thrownBy unpersist(Array[Byte](), Manifest("unknown", 1)) } "throw an UnsupportedDataException when deserializing data with an unsupported version" in { an[UnsupportedDataException] should - be thrownBy unpersist(Persisted("item", 2, ByteString("..."))) + be thrownBy unpersist(Array[Byte](), Manifest("item", 2)) } "throw an UnrecoverableDataException when an exception occurs while deserializing" in { an[UnrecoverableDataException] should - be thrownBy unpersist(Persisted("item", 1, ByteString("not an item"))) + be thrownBy unpersist(ByteString("not an item").toArray, itemPersister.currentManifest) } } } diff --git a/stamina-core/src/test/scala/stamina/StaminaAkkaSerializerSpec.scala b/stamina-core/src/test/scala/stamina/StaminaAkkaSerializerSpec.scala index 65a96e1..5c1ea66 100644 --- a/stamina-core/src/test/scala/stamina/StaminaAkkaSerializerSpec.scala +++ b/stamina-core/src/test/scala/stamina/StaminaAkkaSerializerSpec.scala @@ -25,22 +25,22 @@ class StaminaAkkaSerializerSpec extends StaminaSpec { } "throw an UnregisteredTypeException when serializing an unregistered type" in { - a[UnregisteredTypeException] should be thrownBy toBinary("a raw String is not supported", Manifest("foo", 32)) + a[UnregisteredTypeException] should be thrownBy toBinary(ByteString("a raw String is not supported").toArray) } "throw an UnsupportedDataException when deserializing data with an unknown key" in { an[UnsupportedDataException] should - be thrownBy fromBinary(ByteString("...").toArray, Manifest("unknown", 1).manifest) + be thrownBy fromBinary(Array[Byte](), Manifest("unknown", 1).manifest) } "throw an UnsupportedDataException when deserializing data with an unsupported version" in { an[UnsupportedDataException] should - be thrownBy fromBinary(ByteString("...").toArray, Manifest("item", 2).manifest) + be thrownBy fromBinary(Array[Byte](), Manifest("item", 2).manifest) } "throw an UnrecoverableDataException when an exception occurs while deserializing" in { an[UnrecoverableDataException] should - be thrownBy fromBinary(ByteString("not an item").toArray, Manifest("item", 1).manifest) + be thrownBy fromBinary(ByteString("not an item").toArray, itemPersister.currentManifest.manifest) } } } From c0557b0a86bea3edd393eaebd50b379b161202e1 Mon Sep 17 00:00:00 2001 From: Age Mooij Date: Fri, 19 Feb 2016 12:37:25 +0100 Subject: [PATCH 11/11] minor whitespace tweak --- stamina-core/src/main/scala/stamina/Persister.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stamina-core/src/main/scala/stamina/Persister.scala b/stamina-core/src/main/scala/stamina/Persister.scala index 170be15..0860f2e 100644 --- a/stamina-core/src/main/scala/stamina/Persister.scala +++ b/stamina-core/src/main/scala/stamina/Persister.scala @@ -10,8 +10,8 @@ import scala.util._ */ abstract class Persister[T: ClassTag, V <: Version: VersionInfo](val key: String) { lazy val currentVersion = Version.numberFor[V] - lazy val currentManifest = Manifest(key, currentVersion) + def persist(t: T): Array[Byte] def unpersist(manifest: Manifest, persisted: Array[Byte]): T