2015-05-17 12:28:47 +02:00
|
|
|
/**
|
2017-01-04 17:37:10 +01:00
|
|
|
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
2015-05-17 12:28:47 +02:00
|
|
|
*/
|
|
|
|
|
package akka.cluster.ddata
|
|
|
|
|
|
|
|
|
|
import akka.cluster.Cluster
|
|
|
|
|
import akka.cluster.UniqueAddress
|
2017-02-07 11:21:56 +01:00
|
|
|
import akka.annotation.InternalApi
|
2015-05-17 12:28:47 +02:00
|
|
|
|
|
|
|
|
object LWWMap {
|
2016-12-22 11:47:27 +01:00
|
|
|
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
|
2015-05-17 12:28:47 +02:00
|
|
|
/**
|
|
|
|
|
* Java API
|
|
|
|
|
*/
|
2016-12-22 11:47:27 +01:00
|
|
|
def create[A, B](): LWWMap[A, B] = empty
|
2015-05-17 12:28:47 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extract the [[LWWMap#entries]].
|
|
|
|
|
*/
|
2016-12-22 11:47:27 +01:00
|
|
|
def unapply[A, B](m: LWWMap[A, B]): Option[Map[A, B]] = Some(m.entries)
|
2015-05-17 12:28:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Specialized [[ORMap]] with [[LWWRegister]] values.
|
|
|
|
|
*
|
|
|
|
|
* `LWWRegister` relies on synchronized clocks and should only be used when the choice of
|
|
|
|
|
* value is not important for concurrent updates occurring within the clock skew.
|
|
|
|
|
*
|
|
|
|
|
* Instead of using timestamps based on `System.currentTimeMillis()` time it is possible to
|
|
|
|
|
* use a timestamp value based on something else, for example an increasing version number
|
|
|
|
|
* from a database record that is used for optimistic concurrency control.
|
|
|
|
|
*
|
2016-10-03 07:02:30 -05:00
|
|
|
* The `defaultClock` is using max value of `System.currentTimeMillis()` and `currentTimestamp + 1`.
|
|
|
|
|
* This means that the timestamp is increased for changes on the same node that occurs within
|
|
|
|
|
* the same millisecond. It also means that it is safe to use the `LWWMap` without
|
|
|
|
|
* synchronized clocks when there is only one active writer, e.g. a Cluster Singleton. Such a
|
|
|
|
|
* single writer should then first read current value with `ReadMajority` (or more) before
|
|
|
|
|
* changing and writing the value with `WriteMajority` (or more).
|
|
|
|
|
*
|
2015-05-17 12:28:47 +02:00
|
|
|
* For first-write-wins semantics you can use the [[LWWRegister#reverseClock]] instead of the
|
|
|
|
|
* [[LWWRegister#defaultClock]]
|
|
|
|
|
*
|
|
|
|
|
* This class is immutable, i.e. "modifying" methods return a new instance.
|
|
|
|
|
*/
|
|
|
|
|
@SerialVersionUID(1L)
|
2016-12-22 11:47:27 +01:00
|
|
|
final class LWWMap[A, B] private[akka] (
|
|
|
|
|
private[akka] val underlying: ORMap[A, LWWRegister[B]])
|
2015-05-17 12:28:47 +02:00
|
|
|
extends ReplicatedData with ReplicatedDataSerialization with RemovedNodePruning {
|
|
|
|
|
import LWWRegister.{ Clock, defaultClock }
|
|
|
|
|
|
2016-12-22 11:47:27 +01:00
|
|
|
type T = LWWMap[A, B]
|
2015-05-17 12:28:47 +02:00
|
|
|
|
2015-07-01 09:46:58 +02:00
|
|
|
/**
|
|
|
|
|
* Scala API: All entries of the map.
|
|
|
|
|
*/
|
2016-12-22 11:47:27 +01:00
|
|
|
def entries: Map[A, B] = underlying.entries.map { case (k, r) ⇒ k → r.value }
|
2015-05-17 12:28:47 +02:00
|
|
|
|
2015-07-01 09:46:58 +02:00
|
|
|
/**
|
|
|
|
|
* Java API: All entries of the map.
|
|
|
|
|
*/
|
2016-12-22 11:47:27 +01:00
|
|
|
def getEntries(): java.util.Map[A, B] = {
|
2015-07-01 09:46:58 +02:00
|
|
|
import scala.collection.JavaConverters._
|
|
|
|
|
entries.asJava
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-22 11:47:27 +01:00
|
|
|
def get(key: A): Option[B] = underlying.get(key).map(_.value)
|
2015-05-17 12:28:47 +02:00
|
|
|
|
2016-12-22 11:47:27 +01:00
|
|
|
def contains(key: A): Boolean = underlying.contains(key)
|
2015-05-17 12:28:47 +02:00
|
|
|
|
|
|
|
|
def isEmpty: Boolean = underlying.isEmpty
|
|
|
|
|
|
|
|
|
|
def size: Int = underlying.size
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Adds an entry to the map
|
|
|
|
|
*/
|
2016-12-22 11:47:27 +01:00
|
|
|
def +(entry: (A, B))(implicit node: Cluster): LWWMap[A, B] = {
|
2015-05-17 12:28:47 +02:00
|
|
|
val (key, value) = entry
|
|
|
|
|
put(node, key, value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Adds an entry to the map
|
|
|
|
|
*/
|
2016-12-22 11:47:27 +01:00
|
|
|
def put(node: Cluster, key: A, value: B): LWWMap[A, B] =
|
|
|
|
|
put(node, key, value, defaultClock[B])
|
2015-05-17 12:28:47 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Adds an entry to the map.
|
|
|
|
|
*
|
|
|
|
|
* You can provide your `clock` implementation instead of using timestamps based
|
|
|
|
|
* on `System.currentTimeMillis()` time. The timestamp can for example be an
|
|
|
|
|
* increasing version number from a database record that is used for optimistic
|
|
|
|
|
* concurrency control.
|
|
|
|
|
*/
|
2016-12-22 11:47:27 +01:00
|
|
|
def put(node: Cluster, key: A, value: B, clock: Clock[B]): LWWMap[A, B] =
|
2015-05-17 12:28:47 +02:00
|
|
|
put(node.selfUniqueAddress, key, value, clock)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Adds an entry to the map.
|
|
|
|
|
*
|
|
|
|
|
* You can provide your `clock` implementation instead of using timestamps based
|
|
|
|
|
* on `System.currentTimeMillis()` time. The timestamp can for example be an
|
|
|
|
|
* increasing version number from a database record that is used for optimistic
|
|
|
|
|
* concurrency control.
|
|
|
|
|
*/
|
2016-12-22 11:47:27 +01:00
|
|
|
def put(key: A, value: B)(implicit node: Cluster, clock: Clock[B] = defaultClock[B]): LWWMap[A, B] =
|
2015-05-17 12:28:47 +02:00
|
|
|
put(node, key, value, clock)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2017-02-07 11:21:56 +01:00
|
|
|
@InternalApi private[akka] def put(node: UniqueAddress, key: A, value: B, clock: Clock[B]): LWWMap[A, B] = {
|
2015-05-17 12:28:47 +02:00
|
|
|
val newRegister = underlying.get(key) match {
|
|
|
|
|
case Some(r) ⇒ r.withValue(node, value, clock)
|
|
|
|
|
case None ⇒ LWWRegister(node, value, clock)
|
|
|
|
|
}
|
|
|
|
|
new LWWMap(underlying.put(node, key, newRegister))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2016-12-22 11:47:27 +01:00
|
|
|
def -(key: A)(implicit node: Cluster): LWWMap[A, B] = remove(node, key)
|
2015-05-17 12:28:47 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2016-12-22 11:47:27 +01:00
|
|
|
def remove(node: Cluster, key: A): LWWMap[A, B] =
|
2015-05-17 12:28:47 +02:00
|
|
|
remove(node.selfUniqueAddress, key)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2017-02-07 11:21:56 +01:00
|
|
|
@InternalApi private[akka] def remove(node: UniqueAddress, key: A): LWWMap[A, B] =
|
2015-05-17 12:28:47 +02:00
|
|
|
new LWWMap(underlying.remove(node, key))
|
|
|
|
|
|
2016-12-22 11:47:27 +01:00
|
|
|
override def merge(that: LWWMap[A, B]): LWWMap[A, B] =
|
2015-05-17 12:28:47 +02:00
|
|
|
new LWWMap(underlying.merge(that.underlying))
|
|
|
|
|
|
2017-01-11 13:19:45 +01:00
|
|
|
override def modifiedByNodes: Set[UniqueAddress] =
|
|
|
|
|
underlying.modifiedByNodes
|
|
|
|
|
|
2015-05-17 12:28:47 +02:00
|
|
|
override def needPruningFrom(removedNode: UniqueAddress): Boolean =
|
|
|
|
|
underlying.needPruningFrom(removedNode)
|
|
|
|
|
|
2016-12-22 11:47:27 +01:00
|
|
|
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): LWWMap[A, B] =
|
2015-05-17 12:28:47 +02:00
|
|
|
new LWWMap(underlying.prune(removedNode, collapseInto))
|
|
|
|
|
|
2016-12-22 11:47:27 +01:00
|
|
|
override def pruningCleanup(removedNode: UniqueAddress): LWWMap[A, B] =
|
2015-05-17 12:28:47 +02:00
|
|
|
new LWWMap(underlying.pruningCleanup(removedNode))
|
|
|
|
|
|
|
|
|
|
// this class cannot be a `case class` because we need different `unapply`
|
|
|
|
|
|
|
|
|
|
override def toString: String = s"LWW$entries" //e.g. LWWMap(a -> 1, b -> 2)
|
|
|
|
|
|
|
|
|
|
override def equals(o: Any): Boolean = o match {
|
2016-12-22 11:47:27 +01:00
|
|
|
case other: LWWMap[_, _] ⇒ underlying == other.underlying
|
|
|
|
|
case _ ⇒ false
|
2015-05-17 12:28:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def hashCode: Int = underlying.hashCode
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
object LWWMapKey {
|
2016-12-22 11:47:27 +01:00
|
|
|
def create[A, B](id: String): Key[LWWMap[A, B]] = LWWMapKey(id)
|
2015-05-17 12:28:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SerialVersionUID(1L)
|
2016-12-22 11:47:27 +01:00
|
|
|
final case class LWWMapKey[A, B](_id: String) extends Key[LWWMap[A, B]](_id) with ReplicatedDataSerialization
|