#22035 Make it possible to use anything as the key in a map
- All Map types are now generic in their key: ORMap, ORMultiMap, LWWMap, PNCounterMap - test for binary compatibility with previous version for serialization - entries are sorted for deterministic SHA-1 on same value
This commit is contained in:
parent
5c79b81e92
commit
8499ff6faf
28 changed files with 2231 additions and 584 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -58,8 +58,11 @@ message VersionVector {
|
|||
|
||||
message ORMap {
|
||||
message Entry {
|
||||
required string key = 1;
|
||||
optional string stringKey = 1;
|
||||
required OtherMessage value = 2;
|
||||
optional sint32 intKey = 3;
|
||||
optional sint64 longKey = 4;
|
||||
optional OtherMessage otherKey = 5;
|
||||
}
|
||||
|
||||
required ORSet keys = 1;
|
||||
|
|
@ -68,8 +71,11 @@ message ORMap {
|
|||
|
||||
message LWWMap {
|
||||
message Entry {
|
||||
required string key = 1;
|
||||
optional string stringKey = 1;
|
||||
required LWWRegister value = 2;
|
||||
optional sint32 intKey = 3;
|
||||
optional sint64 longKey = 4;
|
||||
optional OtherMessage otherKey = 5;
|
||||
}
|
||||
|
||||
required ORSet keys = 1;
|
||||
|
|
@ -78,8 +84,11 @@ message LWWMap {
|
|||
|
||||
message PNCounterMap {
|
||||
message Entry {
|
||||
required string key = 1;
|
||||
optional string stringKey = 1;
|
||||
required PNCounter value = 2;
|
||||
optional sint32 intKey = 3;
|
||||
optional sint64 longKey = 4;
|
||||
optional OtherMessage otherKey = 5;
|
||||
}
|
||||
|
||||
required ORSet keys = 1;
|
||||
|
|
@ -88,8 +97,11 @@ message PNCounterMap {
|
|||
|
||||
message ORMultiMap {
|
||||
message Entry {
|
||||
required string key = 1;
|
||||
optional string stringKey = 1;
|
||||
required ORSet value = 2;
|
||||
optional sint32 intKey = 3;
|
||||
optional sint64 longKey = 4;
|
||||
optional OtherMessage otherKey = 5;
|
||||
}
|
||||
|
||||
required ORSet keys = 1;
|
||||
|
|
|
|||
|
|
@ -7,18 +7,18 @@ import akka.cluster.Cluster
|
|||
import akka.cluster.UniqueAddress
|
||||
|
||||
object LWWMap {
|
||||
private val _empty: LWWMap[Any] = new LWWMap(ORMap.empty)
|
||||
def empty[A]: LWWMap[A] = _empty.asInstanceOf[LWWMap[A]]
|
||||
def apply(): LWWMap[Any] = _empty
|
||||
private val _empty: LWWMap[Any, Any] = new LWWMap(ORMap.empty)
|
||||
def empty[A, B]: LWWMap[A, B] = _empty.asInstanceOf[LWWMap[A, B]]
|
||||
def apply(): LWWMap[Any, Any] = _empty
|
||||
/**
|
||||
* Java API
|
||||
*/
|
||||
def create[A](): LWWMap[A] = empty
|
||||
def create[A, B](): LWWMap[A, B] = empty
|
||||
|
||||
/**
|
||||
* Extract the [[LWWMap#entries]].
|
||||
*/
|
||||
def unapply[A](m: LWWMap[A]): Option[Map[String, A]] = Some(m.entries)
|
||||
def unapply[A, B](m: LWWMap[A, B]): Option[Map[A, B]] = Some(m.entries)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -44,29 +44,29 @@ object LWWMap {
|
|||
* This class is immutable, i.e. "modifying" methods return a new instance.
|
||||
*/
|
||||
@SerialVersionUID(1L)
|
||||
final class LWWMap[A] private[akka] (
|
||||
private[akka] val underlying: ORMap[LWWRegister[A]])
|
||||
final class LWWMap[A, B] private[akka] (
|
||||
private[akka] val underlying: ORMap[A, LWWRegister[B]])
|
||||
extends ReplicatedData with ReplicatedDataSerialization with RemovedNodePruning {
|
||||
import LWWRegister.{ Clock, defaultClock }
|
||||
|
||||
type T = LWWMap[A]
|
||||
type T = LWWMap[A, B]
|
||||
|
||||
/**
|
||||
* Scala API: All entries of the map.
|
||||
*/
|
||||
def entries: Map[String, A] = underlying.entries.map { case (k, r) ⇒ k → r.value }
|
||||
def entries: Map[A, B] = underlying.entries.map { case (k, r) ⇒ k → r.value }
|
||||
|
||||
/**
|
||||
* Java API: All entries of the map.
|
||||
*/
|
||||
def getEntries(): java.util.Map[String, A] = {
|
||||
def getEntries(): java.util.Map[A, B] = {
|
||||
import scala.collection.JavaConverters._
|
||||
entries.asJava
|
||||
}
|
||||
|
||||
def get(key: String): Option[A] = underlying.get(key).map(_.value)
|
||||
def get(key: A): Option[B] = underlying.get(key).map(_.value)
|
||||
|
||||
def contains(key: String): Boolean = underlying.contains(key)
|
||||
def contains(key: A): Boolean = underlying.contains(key)
|
||||
|
||||
def isEmpty: Boolean = underlying.isEmpty
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ final class LWWMap[A] private[akka] (
|
|||
/**
|
||||
* Adds an entry to the map
|
||||
*/
|
||||
def +(entry: (String, A))(implicit node: Cluster): LWWMap[A] = {
|
||||
def +(entry: (A, B))(implicit node: Cluster): LWWMap[A, B] = {
|
||||
val (key, value) = entry
|
||||
put(node, key, value)
|
||||
}
|
||||
|
|
@ -83,8 +83,8 @@ final class LWWMap[A] private[akka] (
|
|||
/**
|
||||
* Adds an entry to the map
|
||||
*/
|
||||
def put(node: Cluster, key: String, value: A): LWWMap[A] =
|
||||
put(node, key, value, defaultClock[A])
|
||||
def put(node: Cluster, key: A, value: B): LWWMap[A, B] =
|
||||
put(node, key, value, defaultClock[B])
|
||||
|
||||
/**
|
||||
* Adds an entry to the map.
|
||||
|
|
@ -94,7 +94,7 @@ final class LWWMap[A] private[akka] (
|
|||
* increasing version number from a database record that is used for optimistic
|
||||
* concurrency control.
|
||||
*/
|
||||
def put(node: Cluster, key: String, value: A, clock: Clock[A]): LWWMap[A] =
|
||||
def put(node: Cluster, key: A, value: B, clock: Clock[B]): LWWMap[A, B] =
|
||||
put(node.selfUniqueAddress, key, value, clock)
|
||||
|
||||
/**
|
||||
|
|
@ -105,13 +105,13 @@ final class LWWMap[A] private[akka] (
|
|||
* increasing version number from a database record that is used for optimistic
|
||||
* concurrency control.
|
||||
*/
|
||||
def put(key: String, value: A)(implicit node: Cluster, clock: Clock[A] = defaultClock[A]): LWWMap[A] =
|
||||
def put(key: A, value: B)(implicit node: Cluster, clock: Clock[B] = defaultClock[B]): LWWMap[A, B] =
|
||||
put(node, key, value, clock)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] def put(node: UniqueAddress, key: String, value: A, clock: Clock[A]): LWWMap[A] = {
|
||||
private[akka] def put(node: UniqueAddress, key: A, value: B, clock: Clock[B]): LWWMap[A, B] = {
|
||||
val newRegister = underlying.get(key) match {
|
||||
case Some(r) ⇒ r.withValue(node, value, clock)
|
||||
case None ⇒ LWWRegister(node, value, clock)
|
||||
|
|
@ -124,32 +124,32 @@ final class LWWMap[A] private[akka] (
|
|||
* Note that if there is a conflicting update on another node the entry will
|
||||
* not be removed after merge.
|
||||
*/
|
||||
def -(key: String)(implicit node: Cluster): LWWMap[A] = remove(node, key)
|
||||
def -(key: A)(implicit node: Cluster): LWWMap[A, B] = remove(node, key)
|
||||
|
||||
/**
|
||||
* Removes an entry from the map.
|
||||
* Note that if there is a conflicting update on another node the entry will
|
||||
* not be removed after merge.
|
||||
*/
|
||||
def remove(node: Cluster, key: String): LWWMap[A] =
|
||||
def remove(node: Cluster, key: A): LWWMap[A, B] =
|
||||
remove(node.selfUniqueAddress, key)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] def remove(node: UniqueAddress, key: String): LWWMap[A] =
|
||||
private[akka] def remove(node: UniqueAddress, key: A): LWWMap[A, B] =
|
||||
new LWWMap(underlying.remove(node, key))
|
||||
|
||||
override def merge(that: LWWMap[A]): LWWMap[A] =
|
||||
override def merge(that: LWWMap[A, B]): LWWMap[A, B] =
|
||||
new LWWMap(underlying.merge(that.underlying))
|
||||
|
||||
override def needPruningFrom(removedNode: UniqueAddress): Boolean =
|
||||
underlying.needPruningFrom(removedNode)
|
||||
|
||||
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): LWWMap[A] =
|
||||
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): LWWMap[A, B] =
|
||||
new LWWMap(underlying.prune(removedNode, collapseInto))
|
||||
|
||||
override def pruningCleanup(removedNode: UniqueAddress): LWWMap[A] =
|
||||
override def pruningCleanup(removedNode: UniqueAddress): LWWMap[A, B] =
|
||||
new LWWMap(underlying.pruningCleanup(removedNode))
|
||||
|
||||
// this class cannot be a `case class` because we need different `unapply`
|
||||
|
|
@ -157,16 +157,16 @@ final class LWWMap[A] private[akka] (
|
|||
override def toString: String = s"LWW$entries" //e.g. LWWMap(a -> 1, b -> 2)
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case other: LWWMap[_] ⇒ underlying == other.underlying
|
||||
case _ ⇒ false
|
||||
case other: LWWMap[_, _] ⇒ underlying == other.underlying
|
||||
case _ ⇒ false
|
||||
}
|
||||
|
||||
override def hashCode: Int = underlying.hashCode
|
||||
}
|
||||
|
||||
object LWWMapKey {
|
||||
def create[A](id: String): Key[LWWMap[A]] = LWWMapKey(id)
|
||||
def create[A, B](id: String): Key[LWWMap[A, B]] = LWWMapKey(id)
|
||||
}
|
||||
|
||||
@SerialVersionUID(1L)
|
||||
final case class LWWMapKey[A](_id: String) extends Key[LWWMap[A]](_id) with ReplicatedDataSerialization
|
||||
final case class LWWMapKey[A, B](_id: String) extends Key[LWWMap[A, B]](_id) with ReplicatedDataSerialization
|
||||
|
|
|
|||
|
|
@ -8,18 +8,18 @@ import akka.cluster.UniqueAddress
|
|||
import akka.util.HashCode
|
||||
|
||||
object ORMap {
|
||||
private val _empty: ORMap[ReplicatedData] = new ORMap(ORSet.empty, Map.empty)
|
||||
def empty[A <: ReplicatedData]: ORMap[A] = _empty.asInstanceOf[ORMap[A]]
|
||||
def apply(): ORMap[ReplicatedData] = _empty
|
||||
private val _empty: ORMap[Any, ReplicatedData] = new ORMap(ORSet.empty, Map.empty)
|
||||
def empty[A, B <: ReplicatedData]: ORMap[A, B] = _empty.asInstanceOf[ORMap[A, B]]
|
||||
def apply(): ORMap[Any, ReplicatedData] = _empty
|
||||
/**
|
||||
* Java API
|
||||
*/
|
||||
def create[A <: ReplicatedData](): ORMap[A] = empty[A]
|
||||
def create[A, B <: ReplicatedData](): ORMap[A, B] = empty[A, B]
|
||||
|
||||
/**
|
||||
* Extract the [[ORMap#entries]].
|
||||
*/
|
||||
def unapply[A <: ReplicatedData](m: ORMap[A]): Option[Map[String, A]] = Some(m.entries)
|
||||
def unapply[A, B <: ReplicatedData](m: ORMap[A, B]): Option[Map[A, B]] = Some(m.entries)
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -32,35 +32,35 @@ object ORMap {
|
|||
* This class is immutable, i.e. "modifying" methods return a new instance.
|
||||
*/
|
||||
@SerialVersionUID(1L)
|
||||
final class ORMap[A <: ReplicatedData] private[akka] (
|
||||
private[akka] val keys: ORSet[String],
|
||||
private[akka] val values: Map[String, A])
|
||||
final class ORMap[A, B <: ReplicatedData] private[akka] (
|
||||
private[akka] val keys: ORSet[A],
|
||||
private[akka] val values: Map[A, B])
|
||||
extends ReplicatedData with ReplicatedDataSerialization with RemovedNodePruning {
|
||||
|
||||
type T = ORMap[A]
|
||||
type T = ORMap[A, B]
|
||||
|
||||
/**
|
||||
* Scala API: All entries of the map.
|
||||
*/
|
||||
def entries: Map[String, A] = values
|
||||
def entries: Map[A, B] = values
|
||||
|
||||
/**
|
||||
* Java API: All entries of the map.
|
||||
*/
|
||||
def getEntries(): java.util.Map[String, A] = {
|
||||
def getEntries(): java.util.Map[A, B] = {
|
||||
import scala.collection.JavaConverters._
|
||||
entries.asJava
|
||||
}
|
||||
|
||||
def get(key: String): Option[A] = values.get(key)
|
||||
def get(key: A): Option[B] = values.get(key)
|
||||
|
||||
/**
|
||||
* Scala API: Get the value associated with the key if there is one,
|
||||
* else return the given default.
|
||||
*/
|
||||
def getOrElse(key: String, default: ⇒ A): A = values.getOrElse(key, default)
|
||||
def getOrElse(key: A, default: ⇒ B): B = values.getOrElse(key, default)
|
||||
|
||||
def contains(key: String): Boolean = values.contains(key)
|
||||
def contains(key: A): Boolean = values.contains(key)
|
||||
|
||||
def isEmpty: Boolean = values.isEmpty
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ final class ORMap[A <: ReplicatedData] private[akka] (
|
|||
* Adds an entry to the map
|
||||
* @see [[#put]]
|
||||
*/
|
||||
def +(entry: (String, A))(implicit node: Cluster): ORMap[A] = {
|
||||
def +(entry: (A, B))(implicit node: Cluster): ORMap[A, B] = {
|
||||
val (key, value) = entry
|
||||
put(node, key, value)
|
||||
}
|
||||
|
|
@ -88,12 +88,12 @@ final class ORMap[A <: ReplicatedData] private[akka] (
|
|||
* value, because important history can be lost when replacing the `ORSet` and
|
||||
* undesired effects of merging will occur. Use [[ORMultiMap]] or [[#updated]] instead.
|
||||
*/
|
||||
def put(node: Cluster, key: String, value: A): ORMap[A] = put(node.selfUniqueAddress, key, value)
|
||||
def put(node: Cluster, key: A, value: B): ORMap[A, B] = put(node.selfUniqueAddress, key, value)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] def put(node: UniqueAddress, key: String, value: A): ORMap[A] =
|
||||
private[akka] def put(node: UniqueAddress, key: A, value: B): ORMap[A, B] =
|
||||
if (value.isInstanceOf[ORSet[_]] && values.contains(key))
|
||||
throw new IllegalArgumentException(
|
||||
"`ORMap.put` must not be used to replace an existing `ORSet` " +
|
||||
|
|
@ -108,7 +108,7 @@ final class ORMap[A <: ReplicatedData] private[akka] (
|
|||
* If there is no current value for the `key` the `initial` value will be
|
||||
* passed to the `modify` function.
|
||||
*/
|
||||
def updated(node: Cluster, key: String, initial: A)(modify: A ⇒ A): ORMap[A] =
|
||||
def updated(node: Cluster, key: A, initial: B)(modify: B ⇒ B): ORMap[A, B] =
|
||||
updated(node.selfUniqueAddress, key, initial)(modify)
|
||||
|
||||
/**
|
||||
|
|
@ -117,13 +117,13 @@ final class ORMap[A <: ReplicatedData] private[akka] (
|
|||
* If there is no current value for the `key` the `initial` value will be
|
||||
* passed to the `modify` function.
|
||||
*/
|
||||
def updated(node: Cluster, key: String, initial: A, modify: java.util.function.Function[A, A]): ORMap[A] =
|
||||
def updated(node: Cluster, key: A, initial: B, modify: java.util.function.Function[B, B]): ORMap[A, B] =
|
||||
updated(node, key, initial)(value ⇒ modify.apply(value))
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] def updated(node: UniqueAddress, key: String, initial: A)(modify: A ⇒ A): ORMap[A] = {
|
||||
private[akka] def updated(node: UniqueAddress, key: A, initial: B)(modify: B ⇒ B): ORMap[A, B] = {
|
||||
val newValue = values.get(key) match {
|
||||
case Some(old) ⇒ modify(old)
|
||||
case _ ⇒ modify(initial)
|
||||
|
|
@ -136,25 +136,25 @@ final class ORMap[A <: ReplicatedData] private[akka] (
|
|||
* Note that if there is a conflicting update on another node the entry will
|
||||
* not be removed after merge.
|
||||
*/
|
||||
def -(key: String)(implicit node: Cluster): ORMap[A] = remove(node, key)
|
||||
def -(key: A)(implicit node: Cluster): ORMap[A, B] = remove(node, key)
|
||||
|
||||
/**
|
||||
* Removes an entry from the map.
|
||||
* Note that if there is a conflicting update on another node the entry will
|
||||
* not be removed after merge.
|
||||
*/
|
||||
def remove(node: Cluster, key: String): ORMap[A] = remove(node.selfUniqueAddress, key)
|
||||
def remove(node: Cluster, key: A): ORMap[A, B] = remove(node.selfUniqueAddress, key)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] def remove(node: UniqueAddress, key: String): ORMap[A] = {
|
||||
private[akka] def remove(node: UniqueAddress, key: A): ORMap[A, B] = {
|
||||
new ORMap(keys.remove(node, key), values - key)
|
||||
}
|
||||
|
||||
override def merge(that: ORMap[A]): ORMap[A] = {
|
||||
override def merge(that: ORMap[A, B]): ORMap[A, B] = {
|
||||
val mergedKeys = keys.merge(that.keys)
|
||||
var mergedValues = Map.empty[String, A]
|
||||
var mergedValues = Map.empty[A, B]
|
||||
mergedKeys.elementsMap.keysIterator.foreach { key ⇒
|
||||
(this.values.get(key), that.values.get(key)) match {
|
||||
case (Some(thisValue), Some(thatValue)) ⇒
|
||||
|
|
@ -164,7 +164,7 @@ final class ORMap[A <: ReplicatedData] private[akka] (
|
|||
throw new IllegalArgumentException(errMsg)
|
||||
}
|
||||
// TODO can we get rid of these (safe) casts?
|
||||
val mergedValue = thisValue.merge(thatValue.asInstanceOf[thisValue.T]).asInstanceOf[A]
|
||||
val mergedValue = thisValue.merge(thatValue.asInstanceOf[thisValue.T]).asInstanceOf[B]
|
||||
mergedValues = mergedValues.updated(key, mergedValue)
|
||||
case (Some(thisValue), None) ⇒
|
||||
mergedValues = mergedValues.updated(key, thisValue)
|
||||
|
|
@ -184,21 +184,21 @@ final class ORMap[A <: ReplicatedData] private[akka] (
|
|||
}
|
||||
}
|
||||
|
||||
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): ORMap[A] = {
|
||||
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): ORMap[A, B] = {
|
||||
val prunedKeys = keys.prune(removedNode, collapseInto)
|
||||
val prunedValues = values.foldLeft(values) {
|
||||
case (acc, (key, data: RemovedNodePruning)) if data.needPruningFrom(removedNode) ⇒
|
||||
acc.updated(key, data.prune(removedNode, collapseInto).asInstanceOf[A])
|
||||
acc.updated(key, data.prune(removedNode, collapseInto).asInstanceOf[B])
|
||||
case (acc, _) ⇒ acc
|
||||
}
|
||||
new ORMap(prunedKeys, prunedValues)
|
||||
}
|
||||
|
||||
override def pruningCleanup(removedNode: UniqueAddress): ORMap[A] = {
|
||||
override def pruningCleanup(removedNode: UniqueAddress): ORMap[A, B] = {
|
||||
val pruningCleanupedKeys = keys.pruningCleanup(removedNode)
|
||||
val pruningCleanupedValues = values.foldLeft(values) {
|
||||
case (acc, (key, data: RemovedNodePruning)) if data.needPruningFrom(removedNode) ⇒
|
||||
acc.updated(key, data.pruningCleanup(removedNode).asInstanceOf[A])
|
||||
acc.updated(key, data.pruningCleanup(removedNode).asInstanceOf[B])
|
||||
case (acc, _) ⇒ acc
|
||||
}
|
||||
new ORMap(pruningCleanupedKeys, pruningCleanupedValues)
|
||||
|
|
@ -209,8 +209,8 @@ final class ORMap[A <: ReplicatedData] private[akka] (
|
|||
override def toString: String = s"OR$entries"
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case other: ORMap[_] ⇒ keys == other.keys && values == other.values
|
||||
case _ ⇒ false
|
||||
case other: ORMap[_, _] ⇒ keys == other.keys && values == other.values
|
||||
case _ ⇒ false
|
||||
}
|
||||
|
||||
override def hashCode: Int = {
|
||||
|
|
@ -223,8 +223,8 @@ final class ORMap[A <: ReplicatedData] private[akka] (
|
|||
}
|
||||
|
||||
object ORMapKey {
|
||||
def create[A <: ReplicatedData](id: String): Key[ORMap[A]] = ORMapKey(id)
|
||||
def create[A, B <: ReplicatedData](id: String): Key[ORMap[A, B]] = ORMapKey(id)
|
||||
}
|
||||
|
||||
@SerialVersionUID(1L)
|
||||
final case class ORMapKey[A <: ReplicatedData](_id: String) extends Key[ORMap[A]](_id) with ReplicatedDataSerialization
|
||||
final case class ORMapKey[A, B <: ReplicatedData](_id: String) extends Key[ORMap[A, B]](_id) with ReplicatedDataSerialization
|
||||
|
|
|
|||
|
|
@ -7,29 +7,29 @@ import akka.cluster.{ UniqueAddress, Cluster }
|
|||
|
||||
object ORMultiMap {
|
||||
|
||||
val _empty: ORMultiMap[Any] = new ORMultiMap(ORMap.empty)
|
||||
val _empty: ORMultiMap[Any, Any] = new ORMultiMap(ORMap.empty)
|
||||
/**
|
||||
* Provides an empty multimap.
|
||||
*/
|
||||
def empty[A]: ORMultiMap[A] = _empty.asInstanceOf[ORMultiMap[A]]
|
||||
def apply(): ORMultiMap[Any] = _empty
|
||||
def empty[A, B]: ORMultiMap[A, B] = _empty.asInstanceOf[ORMultiMap[A, B]]
|
||||
def apply(): ORMultiMap[Any, Any] = _empty
|
||||
|
||||
/**
|
||||
* Java API
|
||||
*/
|
||||
def create[A](): ORMultiMap[A] = empty[A]
|
||||
def create[A, B](): ORMultiMap[A, B] = empty[A, B]
|
||||
|
||||
/**
|
||||
* Extract the [[ORMultiMap#entries]].
|
||||
*/
|
||||
def unapply[A](m: ORMultiMap[A]): Option[Map[String, Set[A]]] = Some(m.entries)
|
||||
def unapply[A, B](m: ORMultiMap[A, B]): Option[Map[A, Set[B]]] = Some(m.entries)
|
||||
|
||||
/**
|
||||
* Extract the [[ORMultiMap#entries]] of an `ORMultiMap`.
|
||||
*/
|
||||
def unapply(value: Any): Option[Map[String, Set[Any]]] = value match {
|
||||
case m: ORMultiMap[Any] @unchecked ⇒ Some(m.entries)
|
||||
case _ ⇒ None
|
||||
def unapply[A, B <: ReplicatedData](value: Any): Option[Map[A, Set[B]]] = value match {
|
||||
case m: ORMultiMap[A, B] @unchecked ⇒ Some(m.entries)
|
||||
case _ ⇒ None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -40,10 +40,10 @@ object ORMultiMap {
|
|||
* This class is immutable, i.e. "modifying" methods return a new instance.
|
||||
*/
|
||||
@SerialVersionUID(1L)
|
||||
final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORSet[A]])
|
||||
final class ORMultiMap[A, B] private[akka] (private[akka] val underlying: ORMap[A, ORSet[B]])
|
||||
extends ReplicatedData with ReplicatedDataSerialization with RemovedNodePruning {
|
||||
|
||||
override type T = ORMultiMap[A]
|
||||
override type T = ORMultiMap[A, B]
|
||||
|
||||
override def merge(that: T): T =
|
||||
new ORMultiMap(underlying.merge(that.underlying))
|
||||
|
|
@ -51,15 +51,15 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
|
|||
/**
|
||||
* Scala API: All entries of a multimap where keys are strings and values are sets.
|
||||
*/
|
||||
def entries: Map[String, Set[A]] =
|
||||
def entries: Map[A, Set[B]] =
|
||||
underlying.entries.map { case (k, v) ⇒ k → v.elements }
|
||||
|
||||
/**
|
||||
* Java API: All entries of a multimap where keys are strings and values are sets.
|
||||
*/
|
||||
def getEntries(): java.util.Map[String, java.util.Set[A]] = {
|
||||
def getEntries(): java.util.Map[A, java.util.Set[B]] = {
|
||||
import scala.collection.JavaConverters._
|
||||
val result = new java.util.HashMap[String, java.util.Set[A]]
|
||||
val result = new java.util.HashMap[A, java.util.Set[B]]
|
||||
underlying.entries.foreach {
|
||||
case (k, v) ⇒ result.put(k, v.elements.asJava)
|
||||
}
|
||||
|
|
@ -69,17 +69,17 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
|
|||
/**
|
||||
* Get the set associated with the key if there is one.
|
||||
*/
|
||||
def get(key: String): Option[Set[A]] =
|
||||
def get(key: A): Option[Set[B]] =
|
||||
underlying.get(key).map(_.elements)
|
||||
|
||||
/**
|
||||
* Scala API: Get the set associated with the key if there is one,
|
||||
* else return the given default.
|
||||
*/
|
||||
def getOrElse(key: String, default: ⇒ Set[A]): Set[A] =
|
||||
def getOrElse(key: A, default: ⇒ Set[B]): Set[B] =
|
||||
get(key).getOrElse(default)
|
||||
|
||||
def contains(key: String): Boolean = underlying.contains(key)
|
||||
def contains(key: A): Boolean = underlying.contains(key)
|
||||
|
||||
def isEmpty: Boolean = underlying.isEmpty
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
|
|||
* Convenience for put. Requires an implicit Cluster.
|
||||
* @see [[#put]]
|
||||
*/
|
||||
def +(entry: (String, Set[A]))(implicit node: Cluster): ORMultiMap[A] = {
|
||||
def +(entry: (A, Set[B]))(implicit node: Cluster): ORMultiMap[A, B] = {
|
||||
val (key, value) = entry
|
||||
put(node, key, value)
|
||||
}
|
||||
|
|
@ -98,14 +98,14 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
|
|||
* Scala API: Associate an entire set with the key while retaining the history of the previous
|
||||
* replicated data set.
|
||||
*/
|
||||
def put(node: Cluster, key: String, value: Set[A]): ORMultiMap[A] =
|
||||
def put(node: Cluster, key: A, value: Set[B]): ORMultiMap[A, B] =
|
||||
put(node.selfUniqueAddress, key, value)
|
||||
|
||||
/**
|
||||
* Java API: Associate an entire set with the key while retaining the history of the previous
|
||||
* replicated data set.
|
||||
*/
|
||||
def put(node: Cluster, key: String, value: java.util.Set[A]): ORMultiMap[A] = {
|
||||
def put(node: Cluster, key: A, value: java.util.Set[B]): ORMultiMap[A, B] = {
|
||||
import scala.collection.JavaConverters._
|
||||
put(node, key, value.asScala.toSet)
|
||||
}
|
||||
|
|
@ -113,8 +113,8 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
|
|||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] def put(node: UniqueAddress, key: String, value: Set[A]): ORMultiMap[A] = {
|
||||
val newUnderlying = underlying.updated(node, key, ORSet.empty[A]) { existing ⇒
|
||||
private[akka] def put(node: UniqueAddress, key: A, value: Set[B]): ORMultiMap[A, B] = {
|
||||
val newUnderlying = underlying.updated(node, key, ORSet.empty[B]) { existing ⇒
|
||||
value.foldLeft(existing.clear(node)) { (s, element) ⇒ s.add(node, element) }
|
||||
}
|
||||
new ORMultiMap(newUnderlying)
|
||||
|
|
@ -124,38 +124,38 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
|
|||
* Convenience for remove. Requires an implicit Cluster.
|
||||
* @see [[#remove]]
|
||||
*/
|
||||
def -(key: String)(implicit node: Cluster): ORMultiMap[A] =
|
||||
def -(key: A)(implicit node: Cluster): ORMultiMap[A, B] =
|
||||
remove(node, key)
|
||||
|
||||
/**
|
||||
* Remove an entire set associated with the key.
|
||||
*/
|
||||
def remove(node: Cluster, key: String): ORMultiMap[A] =
|
||||
def remove(node: Cluster, key: A): ORMultiMap[A, B] =
|
||||
remove(node.selfUniqueAddress, key)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] def remove(node: UniqueAddress, key: String): ORMultiMap[A] =
|
||||
private[akka] def remove(node: UniqueAddress, key: A): ORMultiMap[A, B] =
|
||||
new ORMultiMap(underlying.remove(node, key))
|
||||
|
||||
/**
|
||||
* Scala API: Add an element to a set associated with a key. If there is no existing set then one will be initialised.
|
||||
*/
|
||||
def addBinding(key: String, element: A)(implicit node: Cluster): ORMultiMap[A] =
|
||||
def addBinding(key: A, element: B)(implicit node: Cluster): ORMultiMap[A, B] =
|
||||
addBinding(node.selfUniqueAddress, key, element)
|
||||
|
||||
/**
|
||||
* Java API: Add an element to a set associated with a key. If there is no existing set then one will be initialised.
|
||||
*/
|
||||
def addBinding(node: Cluster, key: String, element: A): ORMultiMap[A] =
|
||||
def addBinding(node: Cluster, key: A, element: B): ORMultiMap[A, B] =
|
||||
addBinding(key, element)(node)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] def addBinding(node: UniqueAddress, key: String, element: A): ORMultiMap[A] = {
|
||||
val newUnderlying = underlying.updated(node, key, ORSet.empty[A])(_.add(node, element))
|
||||
private[akka] def addBinding(node: UniqueAddress, key: A, element: B): ORMultiMap[A, B] = {
|
||||
val newUnderlying = underlying.updated(node, key, ORSet.empty[B])(_.add(node, element))
|
||||
new ORMultiMap(newUnderlying)
|
||||
}
|
||||
|
||||
|
|
@ -163,22 +163,22 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
|
|||
* Scala API: Remove an element of a set associated with a key. If there are no more elements in the set then the
|
||||
* entire set will be removed.
|
||||
*/
|
||||
def removeBinding(key: String, element: A)(implicit node: Cluster): ORMultiMap[A] =
|
||||
def removeBinding(key: A, element: B)(implicit node: Cluster): ORMultiMap[A, B] =
|
||||
removeBinding(node.selfUniqueAddress, key, element)
|
||||
|
||||
/**
|
||||
* Java API: Remove an element of a set associated with a key. If there are no more elements in the set then the
|
||||
* entire set will be removed.
|
||||
*/
|
||||
def removeBinding(node: Cluster, key: String, element: A): ORMultiMap[A] =
|
||||
def removeBinding(node: Cluster, key: A, element: B): ORMultiMap[A, B] =
|
||||
removeBinding(key, element)(node)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] def removeBinding(node: UniqueAddress, key: String, element: A): ORMultiMap[A] = {
|
||||
private[akka] def removeBinding(node: UniqueAddress, key: A, element: B): ORMultiMap[A, B] = {
|
||||
val newUnderlying = {
|
||||
val u = underlying.updated(node, key, ORSet.empty[A])(_.remove(node, element))
|
||||
val u = underlying.updated(node, key, ORSet.empty[B])(_.remove(node, element))
|
||||
u.get(key) match {
|
||||
case Some(s) if s.isEmpty ⇒ u.remove(node, key)
|
||||
case _ ⇒ u
|
||||
|
|
@ -192,13 +192,13 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
|
|||
* and another one is added within the same Update. The order of addition and removal is important in order
|
||||
* to retain history for replicated data.
|
||||
*/
|
||||
def replaceBinding(key: String, oldElement: A, newElement: A)(implicit node: Cluster): ORMultiMap[A] =
|
||||
def replaceBinding(key: A, oldElement: B, newElement: B)(implicit node: Cluster): ORMultiMap[A, B] =
|
||||
replaceBinding(node.selfUniqueAddress, key, oldElement, newElement)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] def replaceBinding(node: UniqueAddress, key: String, oldElement: A, newElement: A): ORMultiMap[A] =
|
||||
private[akka] def replaceBinding(node: UniqueAddress, key: A, oldElement: B, newElement: B): ORMultiMap[A, B] =
|
||||
if (newElement != oldElement)
|
||||
addBinding(node, key, newElement).removeBinding(node, key, oldElement)
|
||||
else
|
||||
|
|
@ -218,16 +218,16 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
|
|||
override def toString: String = s"ORMulti$entries"
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case other: ORMultiMap[_] ⇒ underlying == other.underlying
|
||||
case _ ⇒ false
|
||||
case other: ORMultiMap[_, _] ⇒ underlying == other.underlying
|
||||
case _ ⇒ false
|
||||
}
|
||||
|
||||
override def hashCode: Int = underlying.hashCode
|
||||
}
|
||||
|
||||
object ORMultiMapKey {
|
||||
def create[A](id: String): Key[ORMultiMap[A]] = ORMultiMapKey(id)
|
||||
def create[A, B](id: String): Key[ORMultiMap[A, B]] = ORMultiMapKey(id)
|
||||
}
|
||||
|
||||
@SerialVersionUID(1L)
|
||||
final case class ORMultiMapKey[A](_id: String) extends Key[ORMultiMap[A]](_id) with ReplicatedDataSerialization
|
||||
final case class ORMultiMapKey[A, B](_id: String) extends Key[ORMultiMap[A, B]](_id) with ReplicatedDataSerialization
|
||||
|
|
|
|||
|
|
@ -8,17 +8,17 @@ import akka.cluster.UniqueAddress
|
|||
import java.math.BigInteger
|
||||
|
||||
object PNCounterMap {
|
||||
val empty: PNCounterMap = new PNCounterMap(ORMap.empty)
|
||||
def apply(): PNCounterMap = empty
|
||||
def empty[A]: PNCounterMap[A] = new PNCounterMap(ORMap.empty)
|
||||
def apply[A](): PNCounterMap[A] = empty
|
||||
/**
|
||||
* Java API
|
||||
*/
|
||||
def create(): PNCounterMap = empty
|
||||
def create[A](): PNCounterMap[A] = empty
|
||||
|
||||
/**
|
||||
* Extract the [[PNCounterMap#entries]].
|
||||
*/
|
||||
def unapply(m: PNCounterMap): Option[Map[String, BigInt]] = Some(m.entries)
|
||||
def unapply[A](m: PNCounterMap[A]): Option[Map[A, BigInt]] = Some(m.entries)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -27,17 +27,17 @@ object PNCounterMap {
|
|||
* This class is immutable, i.e. "modifying" methods return a new instance.
|
||||
*/
|
||||
@SerialVersionUID(1L)
|
||||
final class PNCounterMap private[akka] (
|
||||
private[akka] val underlying: ORMap[PNCounter])
|
||||
final class PNCounterMap[A] private[akka] (
|
||||
private[akka] val underlying: ORMap[A, PNCounter])
|
||||
extends ReplicatedData with ReplicatedDataSerialization with RemovedNodePruning {
|
||||
|
||||
type T = PNCounterMap
|
||||
type T = PNCounterMap[A]
|
||||
|
||||
/** Scala API */
|
||||
def entries: Map[String, BigInt] = underlying.entries.map { case (k, c) ⇒ k → c.value }
|
||||
def entries: Map[A, BigInt] = underlying.entries.map { case (k, c) ⇒ k → c.value }
|
||||
|
||||
/** Java API */
|
||||
def getEntries: java.util.Map[String, BigInteger] = {
|
||||
def getEntries: java.util.Map[A, BigInteger] = {
|
||||
import scala.collection.JavaConverters._
|
||||
underlying.entries.map { case (k, c) ⇒ k → c.value.bigInteger }.asJava
|
||||
}
|
||||
|
|
@ -45,14 +45,14 @@ final class PNCounterMap private[akka] (
|
|||
/**
|
||||
* Scala API: The count for a key
|
||||
*/
|
||||
def get(key: String): Option[BigInt] = underlying.get(key).map(_.value)
|
||||
def get(key: A): Option[BigInt] = underlying.get(key).map(_.value)
|
||||
|
||||
/**
|
||||
* Java API: The count for a key, or `null` if it doesn't exist
|
||||
*/
|
||||
def getValue(key: String): BigInteger = underlying.get(key).map(_.value.bigInteger).orNull
|
||||
def getValue(key: A): BigInteger = underlying.get(key).map(_.value.bigInteger).orNull
|
||||
|
||||
def contains(key: String): Boolean = underlying.contains(key)
|
||||
def contains(key: A): Boolean = underlying.contains(key)
|
||||
|
||||
def isEmpty: Boolean = underlying.isEmpty
|
||||
|
||||
|
|
@ -62,40 +62,40 @@ final class PNCounterMap private[akka] (
|
|||
* Increment the counter with the delta specified.
|
||||
* If the delta is negative then it will decrement instead of increment.
|
||||
*/
|
||||
def increment(key: String, delta: Long = 1)(implicit node: Cluster): PNCounterMap =
|
||||
def increment(key: A, delta: Long = 1)(implicit node: Cluster): PNCounterMap[A] =
|
||||
increment(node, key, delta)
|
||||
|
||||
/**
|
||||
* Increment the counter with the delta specified.
|
||||
* If the delta is negative then it will decrement instead of increment.
|
||||
*/
|
||||
def increment(node: Cluster, key: String, delta: Long): PNCounterMap =
|
||||
def increment(node: Cluster, key: A, delta: Long): PNCounterMap[A] =
|
||||
increment(node.selfUniqueAddress, key, delta)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] def increment(node: UniqueAddress, key: String, delta: Long): PNCounterMap =
|
||||
private[akka] def increment(node: UniqueAddress, key: A, delta: Long): PNCounterMap[A] =
|
||||
new PNCounterMap(underlying.updated(node, key, PNCounter())(_.increment(node, delta)))
|
||||
|
||||
/**
|
||||
* Decrement the counter with the delta specified.
|
||||
* If the delta is negative then it will increment instead of decrement.
|
||||
*/
|
||||
def decrement(key: String, delta: Long = 1)(implicit node: Cluster): PNCounterMap =
|
||||
def decrement(key: A, delta: Long = 1)(implicit node: Cluster): PNCounterMap[A] =
|
||||
decrement(node, key, delta)
|
||||
|
||||
/**
|
||||
* Decrement the counter with the delta specified.
|
||||
* If the delta is negative then it will increment instead of decrement.
|
||||
*/
|
||||
def decrement(node: Cluster, key: String, delta: Long): PNCounterMap =
|
||||
def decrement(node: Cluster, key: A, delta: Long): PNCounterMap[A] =
|
||||
decrement(node.selfUniqueAddress, key, delta)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] def decrement(node: UniqueAddress, key: String, delta: Long): PNCounterMap = {
|
||||
private[akka] def decrement(node: UniqueAddress, key: A, delta: Long): PNCounterMap[A] = {
|
||||
new PNCounterMap(underlying.updated(node, key, PNCounter())(_.decrement(node, delta)))
|
||||
}
|
||||
|
||||
|
|
@ -104,32 +104,32 @@ final class PNCounterMap private[akka] (
|
|||
* Note that if there is a conflicting update on another node the entry will
|
||||
* not be removed after merge.
|
||||
*/
|
||||
def -(key: String)(implicit node: Cluster): PNCounterMap = remove(node, key)
|
||||
def -(key: A)(implicit node: Cluster): PNCounterMap[A] = remove(node, key)
|
||||
|
||||
/**
|
||||
* Removes an entry from the map.
|
||||
* Note that if there is a conflicting update on another node the entry will
|
||||
* not be removed after merge.
|
||||
*/
|
||||
def remove(node: Cluster, key: String): PNCounterMap =
|
||||
def remove(node: Cluster, key: A): PNCounterMap[A] =
|
||||
remove(node.selfUniqueAddress, key)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] def remove(node: UniqueAddress, key: String): PNCounterMap =
|
||||
private[akka] def remove(node: UniqueAddress, key: A): PNCounterMap[A] =
|
||||
new PNCounterMap(underlying.remove(node, key))
|
||||
|
||||
override def merge(that: PNCounterMap): PNCounterMap =
|
||||
override def merge(that: PNCounterMap[A]): PNCounterMap[A] =
|
||||
new PNCounterMap(underlying.merge(that.underlying))
|
||||
|
||||
override def needPruningFrom(removedNode: UniqueAddress): Boolean =
|
||||
underlying.needPruningFrom(removedNode)
|
||||
|
||||
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): PNCounterMap =
|
||||
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): PNCounterMap[A] =
|
||||
new PNCounterMap(underlying.prune(removedNode, collapseInto))
|
||||
|
||||
override def pruningCleanup(removedNode: UniqueAddress): PNCounterMap =
|
||||
override def pruningCleanup(removedNode: UniqueAddress): PNCounterMap[A] =
|
||||
new PNCounterMap(underlying.pruningCleanup(removedNode))
|
||||
|
||||
// this class cannot be a `case class` because we need different `unapply`
|
||||
|
|
@ -137,16 +137,16 @@ final class PNCounterMap private[akka] (
|
|||
override def toString: String = s"PNCounter$entries"
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case other: PNCounterMap ⇒ underlying == other.underlying
|
||||
case _ ⇒ false
|
||||
case other: PNCounterMap[A] ⇒ underlying == other.underlying
|
||||
case _ ⇒ false
|
||||
}
|
||||
|
||||
override def hashCode: Int = underlying.hashCode
|
||||
}
|
||||
|
||||
object PNCounterMapKey {
|
||||
def create[A](id: String): Key[PNCounterMap] = PNCounterMapKey(id)
|
||||
def create[A](id: String): Key[PNCounterMap[A]] = PNCounterMapKey[A](id)
|
||||
}
|
||||
|
||||
@SerialVersionUID(1L)
|
||||
final case class PNCounterMapKey(_id: String) extends Key[PNCounterMap](_id) with ReplicatedDataSerialization
|
||||
final case class PNCounterMapKey[A](_id: String) extends Key[PNCounterMap[A]](_id) with ReplicatedDataSerialization
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@
|
|||
*/
|
||||
package akka.cluster.ddata.protobuf
|
||||
|
||||
import java.{ lang ⇒ jl }
|
||||
import java.{ util, lang ⇒ jl }
|
||||
import java.util.ArrayList
|
||||
import java.util.Collections
|
||||
import java.util.Comparator
|
||||
import java.util.TreeSet
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.breakOut
|
||||
|
|
@ -17,11 +18,137 @@ import akka.cluster.ddata.protobuf.msg.{ ReplicatedDataMessages ⇒ rd }
|
|||
import akka.cluster.ddata.protobuf.msg.{ ReplicatorMessages ⇒ dm }
|
||||
import akka.serialization.SerializerWithStringManifest
|
||||
import akka.serialization.BaseSerializer
|
||||
import akka.protobuf.ByteString
|
||||
import akka.protobuf.{ ByteString, GeneratedMessage }
|
||||
import akka.util.ByteString.UTF_8
|
||||
|
||||
import scala.collection.immutable.TreeMap
|
||||
import akka.cluster.UniqueAddress
|
||||
import java.io.NotSerializableException
|
||||
import akka.cluster.ddata.protobuf.msg.ReplicatorMessages.OtherMessage
|
||||
|
||||
private object ReplicatedDataSerializer {
|
||||
/*
|
||||
* Generic superclass to allow to compare Entry types used in protobuf.
|
||||
*/
|
||||
abstract class KeyComparator[A <: GeneratedMessage] extends Comparator[A] {
|
||||
/**
|
||||
* Get the key from the entry. The key may be a String, Integer, Long, or Any
|
||||
* @param entry The protobuf entry used with Map types
|
||||
* @return The Key
|
||||
*/
|
||||
def getKey(entry: A): Any
|
||||
final def compare(x: A, y: A): Int = compareKeys(getKey(x), getKey(y))
|
||||
private final def compareKeys(t1: Any, t2: Any): Int = (t1, t2) match {
|
||||
case (k1: String, k2: String) ⇒ k1.compareTo(k2)
|
||||
case (k1: String, k2) ⇒ -1
|
||||
case (k1, k2: String) ⇒ 1
|
||||
case (k1: Int, k2: Int) ⇒ k1.compareTo(k2)
|
||||
case (k1: Int, k2) ⇒ -1
|
||||
case (k1, k2: Int) ⇒ 1
|
||||
case (k1: Long, k2: Long) ⇒ k1.compareTo(k2)
|
||||
case (k1: Long, k2) ⇒ -1
|
||||
case (k1, k2: Long) ⇒ 1
|
||||
case (k1: OtherMessage, k2: OtherMessage) ⇒ OtherMessageComparator.compare(k1, k2)
|
||||
}
|
||||
}
|
||||
|
||||
implicit object ORMapEntryComparator extends KeyComparator[rd.ORMap.Entry] {
|
||||
override def getKey(e: rd.ORMap.Entry): Any = if (e.hasStringKey) e.getStringKey else if (e.hasIntKey) e.getIntKey else if (e.hasLongKey) e.getLongKey else e.getOtherKey
|
||||
}
|
||||
implicit object LWWMapEntryComparator extends KeyComparator[rd.LWWMap.Entry] {
|
||||
override def getKey(e: rd.LWWMap.Entry): Any = if (e.hasStringKey) e.getStringKey else if (e.hasIntKey) e.getIntKey else if (e.hasLongKey) e.getLongKey else e.getOtherKey
|
||||
}
|
||||
implicit object PNCounterMapEntryComparator extends KeyComparator[rd.PNCounterMap.Entry] {
|
||||
override def getKey(e: rd.PNCounterMap.Entry): Any = if (e.hasStringKey) e.getStringKey else if (e.hasIntKey) e.getIntKey else if (e.hasLongKey) e.getLongKey else e.getOtherKey
|
||||
}
|
||||
implicit object ORMultiMapEntryComparator extends KeyComparator[rd.ORMultiMap.Entry] {
|
||||
override def getKey(e: rd.ORMultiMap.Entry): Any = if (e.hasStringKey) e.getStringKey else if (e.hasIntKey) e.getIntKey else if (e.hasLongKey) e.getLongKey else e.getOtherKey
|
||||
}
|
||||
|
||||
sealed trait ProtoMapEntryWriter[Entry <: GeneratedMessage, EntryBuilder <: GeneratedMessage.Builder[EntryBuilder], Value <: GeneratedMessage] {
|
||||
def setStringKey(builder: EntryBuilder, key: String, value: Value): Entry
|
||||
def setLongKey(builder: EntryBuilder, key: Long, value: Value): Entry
|
||||
def setIntKey(builder: EntryBuilder, key: Int, value: Value): Entry
|
||||
def setOtherKey(builder: EntryBuilder, key: dm.OtherMessage, value: Value): Entry
|
||||
}
|
||||
|
||||
sealed trait ProtoMapEntryReader[Entry <: GeneratedMessage, A <: GeneratedMessage] {
|
||||
def hasStringKey(entry: Entry): Boolean
|
||||
def getStringKey(entry: Entry): String
|
||||
def hasIntKey(entry: Entry): Boolean
|
||||
def getIntKey(entry: Entry): Int
|
||||
def hasLongKey(entry: Entry): Boolean
|
||||
def getLongKey(entry: Entry): Long
|
||||
def hasOtherKey(entry: Entry): Boolean
|
||||
def getOtherKey(entry: Entry): dm.OtherMessage
|
||||
def getValue(entry: Entry): A
|
||||
}
|
||||
|
||||
implicit object ORMapEntry extends ProtoMapEntryWriter[rd.ORMap.Entry, rd.ORMap.Entry.Builder, dm.OtherMessage] with ProtoMapEntryReader[rd.ORMap.Entry, dm.OtherMessage] {
|
||||
override def setStringKey(builder: rd.ORMap.Entry.Builder, key: String, value: dm.OtherMessage): rd.ORMap.Entry = builder.setStringKey(key).setValue(value).build()
|
||||
override def setLongKey(builder: rd.ORMap.Entry.Builder, key: Long, value: dm.OtherMessage): rd.ORMap.Entry = builder.setLongKey(key).setValue(value).build()
|
||||
override def setIntKey(builder: rd.ORMap.Entry.Builder, key: Int, value: dm.OtherMessage): rd.ORMap.Entry = builder.setIntKey(key).setValue(value).build()
|
||||
override def setOtherKey(builder: rd.ORMap.Entry.Builder, key: dm.OtherMessage, value: dm.OtherMessage): rd.ORMap.Entry = builder.setOtherKey(key).setValue(value).build()
|
||||
override def hasStringKey(entry: rd.ORMap.Entry): Boolean = entry.hasStringKey
|
||||
override def getStringKey(entry: rd.ORMap.Entry): String = entry.getStringKey
|
||||
override def hasIntKey(entry: rd.ORMap.Entry): Boolean = entry.hasIntKey
|
||||
override def getIntKey(entry: rd.ORMap.Entry): Int = entry.getIntKey
|
||||
override def hasLongKey(entry: rd.ORMap.Entry): Boolean = entry.hasLongKey
|
||||
override def getLongKey(entry: rd.ORMap.Entry): Long = entry.getLongKey
|
||||
override def hasOtherKey(entry: rd.ORMap.Entry): Boolean = entry.hasOtherKey
|
||||
override def getOtherKey(entry: rd.ORMap.Entry): OtherMessage = entry.getOtherKey
|
||||
override def getValue(entry: rd.ORMap.Entry): dm.OtherMessage = entry.getValue
|
||||
}
|
||||
|
||||
implicit object LWWMapEntry extends ProtoMapEntryWriter[rd.LWWMap.Entry, rd.LWWMap.Entry.Builder, rd.LWWRegister] with ProtoMapEntryReader[rd.LWWMap.Entry, rd.LWWRegister] {
|
||||
override def setStringKey(builder: rd.LWWMap.Entry.Builder, key: String, value: rd.LWWRegister): rd.LWWMap.Entry = builder.setStringKey(key).setValue(value).build()
|
||||
override def setLongKey(builder: rd.LWWMap.Entry.Builder, key: Long, value: rd.LWWRegister): rd.LWWMap.Entry = builder.setLongKey(key).setValue(value).build()
|
||||
override def setIntKey(builder: rd.LWWMap.Entry.Builder, key: Int, value: rd.LWWRegister): rd.LWWMap.Entry = builder.setIntKey(key).setValue(value).build()
|
||||
override def setOtherKey(builder: rd.LWWMap.Entry.Builder, key: OtherMessage, value: rd.LWWRegister): rd.LWWMap.Entry = builder.setOtherKey(key).setValue(value).build()
|
||||
override def hasStringKey(entry: rd.LWWMap.Entry): Boolean = entry.hasStringKey
|
||||
override def getStringKey(entry: rd.LWWMap.Entry): String = entry.getStringKey
|
||||
override def hasIntKey(entry: rd.LWWMap.Entry): Boolean = entry.hasIntKey
|
||||
override def getIntKey(entry: rd.LWWMap.Entry): Int = entry.getIntKey
|
||||
override def hasLongKey(entry: rd.LWWMap.Entry): Boolean = entry.hasLongKey
|
||||
override def getLongKey(entry: rd.LWWMap.Entry): Long = entry.getLongKey
|
||||
override def hasOtherKey(entry: rd.LWWMap.Entry): Boolean = entry.hasOtherKey
|
||||
override def getOtherKey(entry: rd.LWWMap.Entry): OtherMessage = entry.getOtherKey
|
||||
override def getValue(entry: rd.LWWMap.Entry): rd.LWWRegister = entry.getValue
|
||||
}
|
||||
|
||||
implicit object PNCounterMapEntry extends ProtoMapEntryWriter[rd.PNCounterMap.Entry, rd.PNCounterMap.Entry.Builder, rd.PNCounter] with ProtoMapEntryReader[rd.PNCounterMap.Entry, rd.PNCounter] {
|
||||
override def setStringKey(builder: rd.PNCounterMap.Entry.Builder, key: String, value: rd.PNCounter): rd.PNCounterMap.Entry = builder.setStringKey(key).setValue(value).build()
|
||||
override def setLongKey(builder: rd.PNCounterMap.Entry.Builder, key: Long, value: rd.PNCounter): rd.PNCounterMap.Entry = builder.setLongKey(key).setValue(value).build()
|
||||
override def setIntKey(builder: rd.PNCounterMap.Entry.Builder, key: Int, value: rd.PNCounter): rd.PNCounterMap.Entry = builder.setIntKey(key).setValue(value).build()
|
||||
override def setOtherKey(builder: rd.PNCounterMap.Entry.Builder, key: OtherMessage, value: rd.PNCounter): rd.PNCounterMap.Entry = builder.setOtherKey(key).setValue(value).build()
|
||||
override def hasStringKey(entry: rd.PNCounterMap.Entry): Boolean = entry.hasStringKey
|
||||
override def getStringKey(entry: rd.PNCounterMap.Entry): String = entry.getStringKey
|
||||
override def hasIntKey(entry: rd.PNCounterMap.Entry): Boolean = entry.hasIntKey
|
||||
override def getIntKey(entry: rd.PNCounterMap.Entry): Int = entry.getIntKey
|
||||
override def hasLongKey(entry: rd.PNCounterMap.Entry): Boolean = entry.hasLongKey
|
||||
override def getLongKey(entry: rd.PNCounterMap.Entry): Long = entry.getLongKey
|
||||
override def hasOtherKey(entry: rd.PNCounterMap.Entry): Boolean = entry.hasOtherKey
|
||||
override def getOtherKey(entry: rd.PNCounterMap.Entry): OtherMessage = entry.getOtherKey
|
||||
override def getValue(entry: rd.PNCounterMap.Entry): rd.PNCounter = entry.getValue
|
||||
}
|
||||
|
||||
implicit object ORMultiMapEntry extends ProtoMapEntryWriter[rd.ORMultiMap.Entry, rd.ORMultiMap.Entry.Builder, rd.ORSet] with ProtoMapEntryReader[rd.ORMultiMap.Entry, rd.ORSet] {
|
||||
override def setStringKey(builder: rd.ORMultiMap.Entry.Builder, key: String, value: rd.ORSet): rd.ORMultiMap.Entry = builder.setStringKey(key).setValue(value).build()
|
||||
override def setLongKey(builder: rd.ORMultiMap.Entry.Builder, key: Long, value: rd.ORSet): rd.ORMultiMap.Entry = builder.setLongKey(key).setValue(value).build()
|
||||
override def setIntKey(builder: rd.ORMultiMap.Entry.Builder, key: Int, value: rd.ORSet): rd.ORMultiMap.Entry = builder.setIntKey(key).setValue(value).build()
|
||||
override def setOtherKey(builder: rd.ORMultiMap.Entry.Builder, key: dm.OtherMessage, value: rd.ORSet): rd.ORMultiMap.Entry = builder.setOtherKey(key).setValue(value).build()
|
||||
override def hasStringKey(entry: rd.ORMultiMap.Entry): Boolean = entry.hasStringKey
|
||||
override def getStringKey(entry: rd.ORMultiMap.Entry): String = entry.getStringKey
|
||||
override def hasIntKey(entry: rd.ORMultiMap.Entry): Boolean = entry.hasIntKey
|
||||
override def getIntKey(entry: rd.ORMultiMap.Entry): Int = entry.getIntKey
|
||||
override def hasLongKey(entry: rd.ORMultiMap.Entry): Boolean = entry.hasLongKey
|
||||
override def getLongKey(entry: rd.ORMultiMap.Entry): Long = entry.getLongKey
|
||||
override def hasOtherKey(entry: rd.ORMultiMap.Entry): Boolean = entry.hasOtherKey
|
||||
override def getOtherKey(entry: rd.ORMultiMap.Entry): OtherMessage = entry.getOtherKey
|
||||
override def getValue(entry: rd.ORMultiMap.Entry): rd.ORSet = entry.getValue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Protobuf serializer of ReplicatedData.
|
||||
|
|
@ -29,6 +156,8 @@ import java.io.NotSerializableException
|
|||
class ReplicatedDataSerializer(val system: ExtendedActorSystem)
|
||||
extends SerializerWithStringManifest with SerializationSupport with BaseSerializer {
|
||||
|
||||
import ReplicatedDataSerializer._
|
||||
|
||||
private val DeletedDataManifest = "A"
|
||||
private val GSetManifest = "B"
|
||||
private val GSetKeyManifest = "b"
|
||||
|
|
@ -78,48 +207,48 @@ class ReplicatedDataSerializer(val system: ExtendedActorSystem)
|
|||
ORMultiMapKeyManifest → (bytes ⇒ ORMultiMapKey(keyIdFromBinary(bytes))))
|
||||
|
||||
override def manifest(obj: AnyRef): String = obj match {
|
||||
case _: ORSet[_] ⇒ ORSetManifest
|
||||
case _: GSet[_] ⇒ GSetManifest
|
||||
case _: GCounter ⇒ GCounterManifest
|
||||
case _: PNCounter ⇒ PNCounterManifest
|
||||
case _: Flag ⇒ FlagManifest
|
||||
case _: LWWRegister[_] ⇒ LWWRegisterManifest
|
||||
case _: ORMap[_] ⇒ ORMapManifest
|
||||
case _: LWWMap[_] ⇒ LWWMapManifest
|
||||
case _: PNCounterMap ⇒ PNCounterMapManifest
|
||||
case _: ORMultiMap[_] ⇒ ORMultiMapManifest
|
||||
case DeletedData ⇒ DeletedDataManifest
|
||||
case _: VersionVector ⇒ VersionVectorManifest
|
||||
case _: ORSet[_] ⇒ ORSetManifest
|
||||
case _: GSet[_] ⇒ GSetManifest
|
||||
case _: GCounter ⇒ GCounterManifest
|
||||
case _: PNCounter ⇒ PNCounterManifest
|
||||
case _: Flag ⇒ FlagManifest
|
||||
case _: LWWRegister[_] ⇒ LWWRegisterManifest
|
||||
case _: ORMap[_, _] ⇒ ORMapManifest
|
||||
case _: LWWMap[_, _] ⇒ LWWMapManifest
|
||||
case _: PNCounterMap[_] ⇒ PNCounterMapManifest
|
||||
case _: ORMultiMap[_, _] ⇒ ORMultiMapManifest
|
||||
case DeletedData ⇒ DeletedDataManifest
|
||||
case _: VersionVector ⇒ VersionVectorManifest
|
||||
|
||||
case _: ORSetKey[_] ⇒ ORSetKeyManifest
|
||||
case _: GSetKey[_] ⇒ GSetKeyManifest
|
||||
case _: GCounterKey ⇒ GCounterKeyManifest
|
||||
case _: PNCounterKey ⇒ PNCounterKeyManifest
|
||||
case _: FlagKey ⇒ FlagKeyManifest
|
||||
case _: LWWRegisterKey[_] ⇒ LWWRegisterKeyManifest
|
||||
case _: ORMapKey[_] ⇒ ORMapKeyManifest
|
||||
case _: LWWMapKey[_] ⇒ LWWMapKeyManifest
|
||||
case _: PNCounterMapKey ⇒ PNCounterMapKeyManifest
|
||||
case _: ORMultiMapKey[_] ⇒ ORMultiMapKeyManifest
|
||||
case _: ORSetKey[_] ⇒ ORSetKeyManifest
|
||||
case _: GSetKey[_] ⇒ GSetKeyManifest
|
||||
case _: GCounterKey ⇒ GCounterKeyManifest
|
||||
case _: PNCounterKey ⇒ PNCounterKeyManifest
|
||||
case _: FlagKey ⇒ FlagKeyManifest
|
||||
case _: LWWRegisterKey[_] ⇒ LWWRegisterKeyManifest
|
||||
case _: ORMapKey[_, _] ⇒ ORMapKeyManifest
|
||||
case _: LWWMapKey[_, _] ⇒ LWWMapKeyManifest
|
||||
case _: PNCounterMapKey[_] ⇒ PNCounterMapKeyManifest
|
||||
case _: ORMultiMapKey[_, _] ⇒ ORMultiMapKeyManifest
|
||||
|
||||
case _ ⇒
|
||||
throw new IllegalArgumentException(s"Can't serialize object of type ${obj.getClass} in [${getClass.getName}]")
|
||||
}
|
||||
|
||||
def toBinary(obj: AnyRef): Array[Byte] = obj match {
|
||||
case m: ORSet[_] ⇒ compress(orsetToProto(m))
|
||||
case m: GSet[_] ⇒ gsetToProto(m).toByteArray
|
||||
case m: GCounter ⇒ gcounterToProto(m).toByteArray
|
||||
case m: PNCounter ⇒ pncounterToProto(m).toByteArray
|
||||
case m: Flag ⇒ flagToProto(m).toByteArray
|
||||
case m: LWWRegister[_] ⇒ lwwRegisterToProto(m).toByteArray
|
||||
case m: ORMap[_] ⇒ compress(ormapToProto(m))
|
||||
case m: LWWMap[_] ⇒ compress(lwwmapToProto(m))
|
||||
case m: PNCounterMap ⇒ compress(pncountermapToProto(m))
|
||||
case m: ORMultiMap[_] ⇒ compress(multimapToProto(m))
|
||||
case DeletedData ⇒ dm.Empty.getDefaultInstance.toByteArray
|
||||
case m: VersionVector ⇒ versionVectorToProto(m).toByteArray
|
||||
case Key(id) ⇒ keyIdToBinary(id)
|
||||
case m: ORSet[_] ⇒ compress(orsetToProto(m))
|
||||
case m: GSet[_] ⇒ gsetToProto(m).toByteArray
|
||||
case m: GCounter ⇒ gcounterToProto(m).toByteArray
|
||||
case m: PNCounter ⇒ pncounterToProto(m).toByteArray
|
||||
case m: Flag ⇒ flagToProto(m).toByteArray
|
||||
case m: LWWRegister[_] ⇒ lwwRegisterToProto(m).toByteArray
|
||||
case m: ORMap[_, _] ⇒ compress(ormapToProto(m))
|
||||
case m: LWWMap[_, _] ⇒ compress(lwwmapToProto(m))
|
||||
case m: PNCounterMap[_] ⇒ compress(pncountermapToProto(m))
|
||||
case m: ORMultiMap[_, _] ⇒ compress(multimapToProto(m))
|
||||
case DeletedData ⇒ dm.Empty.getDefaultInstance.toByteArray
|
||||
case m: VersionVector ⇒ versionVectorToProto(m).toByteArray
|
||||
case Key(id) ⇒ keyIdToBinary(id)
|
||||
case _ ⇒
|
||||
throw new IllegalArgumentException(s"Can't serialize object of type ${obj.getClass} in [${getClass.getName}]")
|
||||
}
|
||||
|
|
@ -328,83 +457,88 @@ class ReplicatedDataSerializer(val system: ExtendedActorSystem)
|
|||
}
|
||||
}
|
||||
|
||||
def ormapToProto(ormap: ORMap[_]): rd.ORMap = {
|
||||
val b = rd.ORMap.newBuilder().setKeys(orsetToProto(ormap.keys))
|
||||
ormap.entries.toVector.sortBy { case (key, _) ⇒ key }.foreach {
|
||||
case (key, value) ⇒ b.addEntries(rd.ORMap.Entry.newBuilder().
|
||||
setKey(key).setValue(otherMessageToProto(value)))
|
||||
/*
|
||||
* Convert a Map[A, B] to an Iterable[Entry] where Entry is the protobuf map entry.
|
||||
*/
|
||||
private def getEntries[IKey, IValue, EntryBuilder <: GeneratedMessage.Builder[EntryBuilder], PEntry <: GeneratedMessage, PValue <: GeneratedMessage](input: Map[IKey, IValue], createBuilder: () ⇒ EntryBuilder, valueConverter: IValue ⇒ PValue)(implicit comparator: Comparator[PEntry], eh: ProtoMapEntryWriter[PEntry, EntryBuilder, PValue]): java.lang.Iterable[PEntry] = {
|
||||
// The resulting Iterable needs to be ordered deterministically in order to create same signature upon serializing same data
|
||||
val protoEntries = new TreeSet[PEntry](comparator)
|
||||
input.foreach {
|
||||
case (key: String, value) ⇒ protoEntries.add(eh.setStringKey(createBuilder(), key, valueConverter(value)))
|
||||
case (key: Int, value) ⇒ protoEntries.add(eh.setIntKey(createBuilder(), key, valueConverter(value)))
|
||||
case (key: Long, value) ⇒ protoEntries.add(eh.setLongKey(createBuilder(), key, valueConverter(value)))
|
||||
case (key, value) ⇒ protoEntries.add(eh.setOtherKey(createBuilder(), otherMessageToProto(key), valueConverter(value)))
|
||||
}
|
||||
b.build()
|
||||
protoEntries
|
||||
}
|
||||
|
||||
def ormapFromBinary(bytes: Array[Byte]): ORMap[ReplicatedData] =
|
||||
def ormapToProto(ormap: ORMap[_, _]): rd.ORMap = {
|
||||
val entries: jl.Iterable[rd.ORMap.Entry] = getEntries(ormap.values, rd.ORMap.Entry.newBuilder, otherMessageToProto)
|
||||
rd.ORMap.newBuilder().setKeys(orsetToProto(ormap.keys)).addAllEntries(entries).build()
|
||||
}
|
||||
|
||||
def ormapFromBinary(bytes: Array[Byte]): ORMap[Any, ReplicatedData] =
|
||||
ormapFromProto(rd.ORMap.parseFrom(decompress(bytes)))
|
||||
|
||||
def ormapFromProto(ormap: rd.ORMap): ORMap[ReplicatedData] = {
|
||||
val entries = ormap.getEntriesList.asScala.map(entry ⇒
|
||||
entry.getKey → otherMessageFromProto(entry.getValue).asInstanceOf[ReplicatedData]).toMap
|
||||
def mapTypeFromProto[PEntry <: GeneratedMessage, A <: GeneratedMessage, B <: ReplicatedData](input: util.List[PEntry], valueCreator: A ⇒ B)(implicit eh: ProtoMapEntryReader[PEntry, A]): Map[Any, B] = {
|
||||
input.asScala.map { entry ⇒
|
||||
if (eh.hasStringKey(entry)) eh.getStringKey(entry) → valueCreator(eh.getValue(entry))
|
||||
else if (eh.hasIntKey(entry)) eh.getIntKey(entry) → valueCreator(eh.getValue(entry))
|
||||
else if (eh.hasLongKey(entry)) eh.getLongKey(entry) → valueCreator(eh.getValue(entry))
|
||||
else if (eh.hasOtherKey(entry)) otherMessageFromProto(eh.getOtherKey(entry)) → valueCreator(eh.getValue(entry))
|
||||
else throw new IllegalArgumentException(s"Can't deserialize ${entry.getClass} because it does not have any key in the serialized message.")
|
||||
}.toMap
|
||||
}
|
||||
|
||||
def ormapFromProto(ormap: rd.ORMap): ORMap[Any, ReplicatedData] = {
|
||||
val entries = mapTypeFromProto(ormap.getEntriesList, (v: dm.OtherMessage) ⇒ otherMessageFromProto(v).asInstanceOf[ReplicatedData])
|
||||
new ORMap(
|
||||
keys = orsetFromProto(ormap.getKeys).asInstanceOf[ORSet[String]],
|
||||
keys = orsetFromProto(ormap.getKeys),
|
||||
entries)
|
||||
}
|
||||
|
||||
def lwwmapToProto(lwwmap: LWWMap[_]): rd.LWWMap = {
|
||||
val b = rd.LWWMap.newBuilder().setKeys(orsetToProto(lwwmap.underlying.keys))
|
||||
lwwmap.underlying.entries.toVector.sortBy { case (key, _) ⇒ key }.foreach {
|
||||
case (key, value) ⇒ b.addEntries(rd.LWWMap.Entry.newBuilder().
|
||||
setKey(key).setValue(lwwRegisterToProto(value)))
|
||||
}
|
||||
b.build()
|
||||
def lwwmapToProto(lwwmap: LWWMap[_, _]): rd.LWWMap = {
|
||||
val entries: jl.Iterable[rd.LWWMap.Entry] = getEntries(lwwmap.underlying.entries, rd.LWWMap.Entry.newBuilder, lwwRegisterToProto)
|
||||
rd.LWWMap.newBuilder().setKeys(orsetToProto(lwwmap.underlying.keys)).addAllEntries(entries).build()
|
||||
}
|
||||
|
||||
def lwwmapFromBinary(bytes: Array[Byte]): LWWMap[Any] =
|
||||
def lwwmapFromBinary(bytes: Array[Byte]): LWWMap[Any, Any] =
|
||||
lwwmapFromProto(rd.LWWMap.parseFrom(decompress(bytes)))
|
||||
|
||||
def lwwmapFromProto(lwwmap: rd.LWWMap): LWWMap[Any] = {
|
||||
val entries = lwwmap.getEntriesList.asScala.map(entry ⇒
|
||||
entry.getKey → lwwRegisterFromProto(entry.getValue)).toMap
|
||||
def lwwmapFromProto(lwwmap: rd.LWWMap): LWWMap[Any, Any] = {
|
||||
val entries = mapTypeFromProto(lwwmap.getEntriesList, lwwRegisterFromProto)
|
||||
new LWWMap(new ORMap(
|
||||
keys = orsetFromProto(lwwmap.getKeys).asInstanceOf[ORSet[String]],
|
||||
keys = orsetFromProto(lwwmap.getKeys),
|
||||
entries))
|
||||
}
|
||||
|
||||
def pncountermapToProto(pncountermap: PNCounterMap): rd.PNCounterMap = {
|
||||
val b = rd.PNCounterMap.newBuilder().setKeys(orsetToProto(pncountermap.underlying.keys))
|
||||
pncountermap.underlying.entries.toVector.sortBy { case (key, _) ⇒ key }.foreach {
|
||||
case (key, value: PNCounter) ⇒ b.addEntries(rd.PNCounterMap.Entry.newBuilder().
|
||||
setKey(key).setValue(pncounterToProto(value)))
|
||||
}
|
||||
b.build()
|
||||
def pncountermapToProto(pncountermap: PNCounterMap[_]): rd.PNCounterMap = {
|
||||
val entries: jl.Iterable[rd.PNCounterMap.Entry] = getEntries(pncountermap.underlying.entries, rd.PNCounterMap.Entry.newBuilder, pncounterToProto)
|
||||
rd.PNCounterMap.newBuilder().setKeys(orsetToProto(pncountermap.underlying.keys)).addAllEntries(entries).build()
|
||||
}
|
||||
|
||||
def pncountermapFromBinary(bytes: Array[Byte]): PNCounterMap =
|
||||
def pncountermapFromBinary(bytes: Array[Byte]): PNCounterMap[_] =
|
||||
pncountermapFromProto(rd.PNCounterMap.parseFrom(decompress(bytes)))
|
||||
|
||||
def pncountermapFromProto(pncountermap: rd.PNCounterMap): PNCounterMap = {
|
||||
val entries = pncountermap.getEntriesList.asScala.map(entry ⇒
|
||||
entry.getKey → pncounterFromProto(entry.getValue)).toMap
|
||||
def pncountermapFromProto(pncountermap: rd.PNCounterMap): PNCounterMap[_] = {
|
||||
val entries = mapTypeFromProto(pncountermap.getEntriesList, pncounterFromProto)
|
||||
new PNCounterMap(new ORMap(
|
||||
keys = orsetFromProto(pncountermap.getKeys).asInstanceOf[ORSet[String]],
|
||||
keys = orsetFromProto(pncountermap.getKeys),
|
||||
entries))
|
||||
}
|
||||
|
||||
def multimapToProto(multimap: ORMultiMap[_]): rd.ORMultiMap = {
|
||||
val b = rd.ORMultiMap.newBuilder().setKeys(orsetToProto(multimap.underlying.keys))
|
||||
multimap.underlying.entries.toVector.sortBy { case (key, _) ⇒ key }.foreach {
|
||||
case (key, value) ⇒ b.addEntries(rd.ORMultiMap.Entry.newBuilder().
|
||||
setKey(key).setValue(orsetToProto(value)))
|
||||
}
|
||||
b.build()
|
||||
def multimapToProto(multimap: ORMultiMap[_, _]): rd.ORMultiMap = {
|
||||
val entries: jl.Iterable[rd.ORMultiMap.Entry] = getEntries(multimap.underlying.entries, rd.ORMultiMap.Entry.newBuilder, orsetToProto)
|
||||
rd.ORMultiMap.newBuilder().setKeys(orsetToProto(multimap.underlying.keys)).addAllEntries(entries).build()
|
||||
}
|
||||
|
||||
def multimapFromBinary(bytes: Array[Byte]): ORMultiMap[Any] =
|
||||
def multimapFromBinary(bytes: Array[Byte]): ORMultiMap[Any, Any] =
|
||||
multimapFromProto(rd.ORMultiMap.parseFrom(decompress(bytes)))
|
||||
|
||||
def multimapFromProto(multimap: rd.ORMultiMap): ORMultiMap[Any] = {
|
||||
val entries = multimap.getEntriesList.asScala.map(entry ⇒
|
||||
entry.getKey → orsetFromProto(entry.getValue)).toMap
|
||||
def multimapFromProto(multimap: rd.ORMultiMap): ORMultiMap[Any, Any] = {
|
||||
val entries = mapTypeFromProto(multimap.getEntriesList, orsetFromProto)
|
||||
new ORMultiMap(new ORMap(
|
||||
keys = orsetFromProto(multimap.getKeys).asInstanceOf[ORSet[String]],
|
||||
keys = orsetFromProto(multimap.getKeys),
|
||||
entries))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class ReplicatorPruningSpec extends MultiNodeSpec(ReplicatorPruningSpec) with ST
|
|||
|
||||
val KeyA = GCounterKey("A")
|
||||
val KeyB = ORSetKey[String]("B")
|
||||
val KeyC = PNCounterMapKey("C")
|
||||
val KeyC = PNCounterMapKey[String]("C")
|
||||
|
||||
def join(from: RoleName, to: RoleName): Unit = {
|
||||
runOn(from) {
|
||||
|
|
@ -86,7 +86,7 @@ class ReplicatorPruningSpec extends MultiNodeSpec(ReplicatorPruningSpec) with ST
|
|||
replicator ! Update(KeyB, ORSet(), WriteAll(timeout))(_ + "a" + "b" + "c")
|
||||
expectMsg(UpdateSuccess(KeyB, None))
|
||||
|
||||
replicator ! Update(KeyC, PNCounterMap(), WriteAll(timeout))(_ increment "x" increment "y")
|
||||
replicator ! Update(KeyC, PNCounterMap.empty[String], WriteAll(timeout)) { _ increment "x" increment "y" }
|
||||
expectMsg(UpdateSuccess(KeyC, None))
|
||||
|
||||
enterBarrier("updates-done")
|
||||
|
|
@ -100,7 +100,7 @@ class ReplicatorPruningSpec extends MultiNodeSpec(ReplicatorPruningSpec) with ST
|
|||
oldSet.elements should be(Set("a", "b", "c"))
|
||||
|
||||
replicator ! Get(KeyC, ReadLocal)
|
||||
val oldMap = expectMsgType[GetSuccess[PNCounterMap]].dataValue
|
||||
val oldMap = expectMsgType[GetSuccess[PNCounterMap[String]]].dataValue
|
||||
oldMap.get("x") should be(Some(3))
|
||||
oldMap.get("y") should be(Some(3))
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class ReplicatorSpec extends MultiNodeSpec(ReplicatorSpec) with STMultiNodeSpec
|
|||
val KeyE2 = GCounterKey("E2")
|
||||
val KeyF = GCounterKey("F")
|
||||
val KeyG = ORSetKey[String]("G")
|
||||
val KeyH = ORMapKey[Flag]("H")
|
||||
val KeyH = ORMapKey[String, Flag]("H")
|
||||
val KeyI = GSetKey[String]("I")
|
||||
val KeyJ = GSetKey[String]("J")
|
||||
val KeyX = GCounterKey("X")
|
||||
|
|
@ -526,20 +526,20 @@ class ReplicatorSpec extends MultiNodeSpec(ReplicatorSpec) with STMultiNodeSpec
|
|||
|
||||
runOn(second) {
|
||||
replicator ! Subscribe(KeyH, changedProbe.ref)
|
||||
replicator ! Update(KeyH, ORMap.empty[Flag], writeTwo)(_ + ("a" → Flag(enabled = false)))
|
||||
replicator ! Update(KeyH, ORMap.empty[String, Flag], writeTwo)(_ + ("a" → Flag(enabled = false)))
|
||||
changedProbe.expectMsgPF() { case c @ Changed(KeyH) ⇒ c.get(KeyH).entries } should be(Map("a" → Flag(enabled = false)))
|
||||
}
|
||||
|
||||
enterBarrier("update-h1")
|
||||
|
||||
runOn(first) {
|
||||
replicator ! Update(KeyH, ORMap.empty[Flag], writeTwo)(_ + ("a" → Flag(enabled = true)))
|
||||
replicator ! Update(KeyH, ORMap.empty[String, Flag], writeTwo)(_ + ("a" → Flag(enabled = true)))
|
||||
}
|
||||
|
||||
runOn(second) {
|
||||
changedProbe.expectMsgPF() { case c @ Changed(KeyH) ⇒ c.get(KeyH).entries } should be(Map("a" → Flag(enabled = true)))
|
||||
|
||||
replicator ! Update(KeyH, ORMap.empty[Flag], writeTwo)(_ + ("b" → Flag(enabled = true)))
|
||||
replicator ! Update(KeyH, ORMap.empty[String, Flag], writeTwo)(_ + ("b" → Flag(enabled = true)))
|
||||
changedProbe.expectMsgPF() { case c @ Changed(KeyH) ⇒ c.get(KeyH).entries } should be(
|
||||
Map("a" → Flag(enabled = true), "b" → Flag(enabled = true)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class LWWMapSpec extends WordSpec with Matchers {
|
|||
"A LWWMap" must {
|
||||
|
||||
"be able to set entries" in {
|
||||
val m = LWWMap.empty[Int].put(node1, "a", 1, defaultClock[Int]).put(node2, "b", 2, defaultClock[Int])
|
||||
val m = LWWMap.empty[String, Int].put(node1, "a", 1, defaultClock[Int]).put(node2, "b", 2, defaultClock[Int])
|
||||
m.entries should be(Map("a" → 1, "b" → 2))
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ class LWWMapSpec extends WordSpec with Matchers {
|
|||
val m1 = LWWMap.empty.put(node1, "a", 1L, defaultClock[Long])
|
||||
val LWWMap(entries1) = m1
|
||||
val entries2: Map[String, Long] = entries1
|
||||
Changed(LWWMapKey[Long]("key"))(m1) match {
|
||||
Changed(LWWMapKey[String, Long]("key"))(m1) match {
|
||||
case c @ Changed(LWWMapKey("key")) ⇒
|
||||
val LWWMap(entries3) = c.dataValue
|
||||
val entries4: Map[String, Long] = entries3
|
||||
|
|
|
|||
|
|
@ -119,11 +119,11 @@ class ORMapSpec extends WordSpec with Matchers {
|
|||
}
|
||||
|
||||
"be able to update entry" in {
|
||||
val m1 = ORMap.empty[ORSet[String]].put(node1, "a", ORSet.empty.add(node1, "A"))
|
||||
val m1 = ORMap.empty[String, ORSet[String]].put(node1, "a", ORSet.empty.add(node1, "A"))
|
||||
.put(node1, "b", ORSet.empty.add(node1, "B01").add(node1, "B02").add(node1, "B03"))
|
||||
val m2 = ORMap.empty[ORSet[String]].put(node2, "c", ORSet.empty.add(node2, "C"))
|
||||
val m2 = ORMap.empty[String, ORSet[String]].put(node2, "c", ORSet.empty.add(node2, "C"))
|
||||
|
||||
val merged1: ORMap[ORSet[String]] = m1 merge m2
|
||||
val merged1: ORMap[String, ORSet[String]] = m1 merge m2
|
||||
|
||||
val m3 = merged1.updated(node1, "b", ORSet.empty[String])(_.clear(node1).add(node1, "B2"))
|
||||
|
||||
|
|
@ -140,11 +140,11 @@ class ORMapSpec extends WordSpec with Matchers {
|
|||
}
|
||||
|
||||
"be able to update ORSet entry with remove+put" in {
|
||||
val m1 = ORMap.empty[ORSet[String]].put(node1, "a", ORSet.empty.add(node1, "A01"))
|
||||
val m1 = ORMap.empty[String, ORSet[String]].put(node1, "a", ORSet.empty.add(node1, "A01"))
|
||||
.updated(node1, "a", ORSet.empty[String])(_.add(node1, "A02"))
|
||||
.updated(node1, "a", ORSet.empty[String])(_.add(node1, "A03"))
|
||||
.put(node1, "b", ORSet.empty.add(node1, "B01").add(node1, "B02").add(node1, "B03"))
|
||||
val m2 = ORMap.empty[ORSet[String]].put(node2, "c", ORSet.empty.add(node2, "C"))
|
||||
val m2 = ORMap.empty[String, ORSet[String]].put(node2, "c", ORSet.empty.add(node2, "C"))
|
||||
|
||||
val merged1 = m1 merge m2
|
||||
|
||||
|
|
@ -190,10 +190,10 @@ class ORMapSpec extends WordSpec with Matchers {
|
|||
|
||||
"have unapply extractor" in {
|
||||
val m1 = ORMap.empty.put(node1, "a", Flag(true)).put(node2, "b", Flag(false))
|
||||
val m2: ORMap[Flag] = m1
|
||||
val m2: ORMap[String, Flag] = m1
|
||||
val ORMap(entries1) = m1
|
||||
val entries2: Map[String, Flag] = entries1
|
||||
Changed(ORMapKey[Flag]("key"))(m1) match {
|
||||
Changed(ORMapKey[String, Flag]("key"))(m1) match {
|
||||
case c @ Changed(ORMapKey("key")) ⇒
|
||||
val ORMap(entries3) = c.dataValue
|
||||
val entries4: Map[String, ReplicatedData] = entries3
|
||||
|
|
|
|||
|
|
@ -109,10 +109,10 @@ class ORMultiMapSpec extends WordSpec with Matchers {
|
|||
|
||||
"have unapply extractor" in {
|
||||
val m1 = ORMultiMap.empty.put(node1, "a", Set(1L, 2L)).put(node2, "b", Set(3L))
|
||||
val m2: ORMultiMap[Long] = m1
|
||||
val m2: ORMultiMap[String, Long] = m1
|
||||
val ORMultiMap(entries1) = m1
|
||||
val entries2: Map[String, Set[Long]] = entries1
|
||||
Changed(ORMultiMapKey[Long]("key"))(m1) match {
|
||||
Changed(ORMultiMapKey[String, Long]("key"))(m1) match {
|
||||
case c @ Changed(ORMultiMapKey("key")) ⇒
|
||||
val ORMultiMap(entries3) = c.dataValue
|
||||
val entries4: Map[String, Set[Long]] = entries3
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class PNCounterMapSpec extends WordSpec with Matchers {
|
|||
val m1 = PNCounterMap.empty.increment(node1, "a", 1).increment(node2, "b", 2)
|
||||
val PNCounterMap(entries1) = m1
|
||||
val entries2: Map[String, BigInt] = entries1
|
||||
Changed(PNCounterMapKey("key"))(m1) match {
|
||||
Changed(PNCounterMapKey[String]("key"))(m1) match {
|
||||
case c @ Changed(PNCounterMapKey("key")) ⇒
|
||||
val PNCounterMap(entries3) = c.dataValue
|
||||
val entries4: Map[String, BigInt] = entries3
|
||||
|
|
|
|||
|
|
@ -3,24 +3,16 @@
|
|||
*/
|
||||
package akka.cluster.ddata.protobuf
|
||||
|
||||
import java.util.Base64
|
||||
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
import org.scalatest.Matchers
|
||||
import org.scalatest.WordSpecLike
|
||||
import akka.actor.ActorSystem
|
||||
import akka.actor.Address
|
||||
import akka.actor.ExtendedActorSystem
|
||||
import akka.cluster.ddata.Flag
|
||||
import akka.cluster.ddata.GCounter
|
||||
import akka.cluster.ddata.GSet
|
||||
import akka.cluster.ddata.LWWMap
|
||||
import akka.cluster.ddata.LWWRegister
|
||||
import akka.cluster.ddata.ORMap
|
||||
import akka.cluster.ddata.ORMultiMap
|
||||
import akka.cluster.ddata.ORSet
|
||||
import akka.cluster.ddata.PNCounter
|
||||
import akka.cluster.ddata.PNCounterMap
|
||||
import akka.cluster.ddata._
|
||||
import akka.cluster.ddata.Replicator.Internal._
|
||||
import akka.cluster.ddata.VersionVector
|
||||
import akka.testkit.TestKit
|
||||
import akka.cluster.UniqueAddress
|
||||
import akka.remote.RARP
|
||||
|
|
@ -46,6 +38,17 @@ class ReplicatedDataSerializerSpec extends TestKit(ActorSystem(
|
|||
shutdown()
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a blob created with the previous serializer (with only string keys for maps). If we deserialize it and then
|
||||
* serialize it again and arive at the same BLOB we can assume that we are compatible in both directions.
|
||||
*/
|
||||
def checkCompatibility(oldBlobAsBase64: String, obj: AnyRef): Unit = {
|
||||
val oldBlob = Base64.getDecoder.decode(oldBlobAsBase64)
|
||||
val deserialized = serializer.fromBinary(oldBlob, serializer.manifest(obj))
|
||||
val newBlob = serializer.toBinary(deserialized)
|
||||
newBlob should equal(oldBlob)
|
||||
}
|
||||
|
||||
def checkSerialization(obj: AnyRef): Unit = {
|
||||
val blob = serializer.toBinary(obj)
|
||||
val ref = serializer.fromBinary(blob, serializer.manifest(obj))
|
||||
|
|
@ -140,38 +143,73 @@ class ReplicatedDataSerializerSpec extends TestKit(ActorSystem(
|
|||
}
|
||||
|
||||
"serialize ORMap" in {
|
||||
checkSerialization(ORMap())
|
||||
checkSerialization(ORMap().put(address1, "a", GSet() + "A"))
|
||||
checkSerialization(ORMap().put(address1, "a", GSet() + "A").put(address2, "b", GSet() + "B"))
|
||||
checkSerialization(ORMap().put(address1, 1, GSet() + "A"))
|
||||
checkSerialization(ORMap().put(address1, 1L, GSet() + "A"))
|
||||
// use Flag for this test as object key because it is serializable
|
||||
checkSerialization(ORMap().put(address1, Flag(), GSet() + "A"))
|
||||
}
|
||||
|
||||
"be compatible with old ORMap serialization" in {
|
||||
// Below blob was created with previous version of the serializer
|
||||
val oldBlobAsBase64 = "H4sIAAAAAAAAAOOax8jlyaXMJc8lzMWXX5KRWqSXkV9copdflC7wXEWUiYGBQRaIGQQkuJS45LiEuHiL83NTUdQwwtWIC6kQpUqVKAulGBOlGJOE+LkYE4W4uJi5GB0FuJUYnUACSRABJ7AAAOLO3C3DAAAA"
|
||||
checkCompatibility(oldBlobAsBase64, ORMap())
|
||||
}
|
||||
|
||||
"serialize LWWMap" in {
|
||||
checkSerialization(LWWMap())
|
||||
checkSerialization(LWWMap().put(address1, "a", "value1", LWWRegister.defaultClock[Any]))
|
||||
checkSerialization(LWWMap().put(address1, 1, "value1", LWWRegister.defaultClock[Any]))
|
||||
checkSerialization(LWWMap().put(address1, 1L, "value1", LWWRegister.defaultClock[Any]))
|
||||
checkSerialization(LWWMap().put(address1, Flag(), "value1", LWWRegister.defaultClock[Any]))
|
||||
checkSerialization(LWWMap().put(address1, "a", "value1", LWWRegister.defaultClock[Any])
|
||||
.put(address2, "b", 17, LWWRegister.defaultClock[Any]))
|
||||
}
|
||||
|
||||
"be compatible with old LWWMap serialization" in {
|
||||
// Below blob was created with previous version of the serializer
|
||||
val oldBlobAsBase64 = "H4sIAAAAAAAAAOPy51LhUuKS4xLi4i3Oz03Vy8gvLtHLL0oXeK4iysjAwCALxAwC0kJEqZJiTBSy5wISVhwzrl2fuyRMiIAWKUEu3jVvGVhLGNjKEnNKUw0FGAG1K/3VkgAAAA=="
|
||||
checkCompatibility(oldBlobAsBase64, LWWMap())
|
||||
}
|
||||
|
||||
"serialize PNCounterMap" in {
|
||||
checkSerialization(PNCounterMap())
|
||||
checkSerialization(PNCounterMap().increment(address1, "a", 3))
|
||||
checkSerialization(PNCounterMap().increment(address1, 1, 3))
|
||||
checkSerialization(PNCounterMap().increment(address1, 1L, 3))
|
||||
checkSerialization(PNCounterMap().increment(address1, Flag(), 3))
|
||||
checkSerialization(PNCounterMap().increment(address1, "a", 3).decrement(address2, "a", 2).
|
||||
increment(address2, "b", 5))
|
||||
}
|
||||
|
||||
"be compatible with old PNCounterMap serialization" in {
|
||||
// Below blob was created with previous version of the serializer
|
||||
val oldBlobAsBase64 = "H4sIAAAAAAAAAOPy51LhUuKS4xLi4i3Oz03Vy8gvLtHLL0oXeK4iysjAwCALxAwC8kJEqZJiTBTS4wISmlyqXMqE1AsxMgsxAADYQs/9gQAAAA=="
|
||||
checkCompatibility(oldBlobAsBase64, PNCounterMap())
|
||||
}
|
||||
|
||||
"serialize ORMultiMap" in {
|
||||
checkSerialization(ORMultiMap())
|
||||
checkSerialization(ORMultiMap().addBinding(address1, "a", "A"))
|
||||
checkSerialization(ORMultiMap.empty[String]
|
||||
checkSerialization(ORMultiMap().addBinding(address1, 1, "A"))
|
||||
checkSerialization(ORMultiMap().addBinding(address1, 1L, "A"))
|
||||
checkSerialization(ORMultiMap().addBinding(address1, Flag(), "A"))
|
||||
checkSerialization(ORMultiMap.empty[String, String]
|
||||
.addBinding(address1, "a", "A1")
|
||||
.put(address2, "b", Set("B1", "B2", "B3"))
|
||||
.addBinding(address2, "a", "A2"))
|
||||
|
||||
val m1 = ORMultiMap.empty[String].addBinding(address1, "a", "A1").addBinding(address2, "a", "A2")
|
||||
val m2 = ORMultiMap.empty[String].put(address2, "b", Set("B1", "B2", "B3"))
|
||||
val m1 = ORMultiMap.empty[String, String].addBinding(address1, "a", "A1").addBinding(address2, "a", "A2")
|
||||
val m2 = ORMultiMap.empty[String, String].put(address2, "b", Set("B1", "B2", "B3"))
|
||||
checkSameContent(m1.merge(m2), m2.merge(m1))
|
||||
}
|
||||
|
||||
"be compatible with old ORMultiMap serialization" in {
|
||||
// Below blob was created with previous version of the serializer
|
||||
val oldBlobAsBase64 = "H4sIAAAAAAAAAOPy51LhUuKS4xLi4i3Oz03Vy8gvLtHLL0oXeK4iysjAwCALxAwCakJEqZJiTBQK4QISxJmqSpSpqlKMjgDlsHjDpwAAAA=="
|
||||
checkCompatibility(oldBlobAsBase64, ORMultiMap())
|
||||
}
|
||||
|
||||
"serialize DeletedData" in {
|
||||
checkSerialization(DeletedData)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue