+case class ResourceInstanceSnapshot(/**
+ * Same as `resource` of [[gr.grnet.aquarium.logic.events.ResourceEvent]]
+ */
+ resource: String,
+
+ /**
+ * Same as `instanceId` of [[gr.grnet.aquarium.logic.events.ResourceEvent]]
+ */
+ instanceId: String,
+ data: Double, // FIXME: data is a missleading name here
+ snapshotTime: Long)
+ extends UserDataSnapshot[Double] {
+
+ /**
+ * This is the amount kept for the resource instance.
+ *
+ * The general rule is that an amount saved in a [[gr.grnet.aquarium.user.ResourceInstanceSnapshot]] represents a
+ * total value, while a value appearing in a [[gr.grnet.aquarium.logic.events.ResourceEvent]] represents a
+ * difference. How these two values are combined to form the new amount is dictated by the underlying
+ * [[gr.grnet.aquarium.logic.accounting.dsl.DSLCostPolicy]]
+ */
+ def instanceAmount = data
+
+ def isSameResourceInstance(resource: String, instanceId: String) = {
+ this.resource == resource &&
+ this.instanceId == instanceId
+ }
+}
+
+/**
+ * A map from (resourceName, resourceInstanceId) to (value, snapshotTime).
+ * This representation is convenient for computations and updating, while the
+ * [[gr.grnet.aquarium.user.OwnedResourcesSnapshot]] representation is convenient for JSON serialization.
+ */
+class OwnedResourcesMap(resourcesMap: Map[(String, String), (Double, Long)]) {
+ def toResourcesSnapshot(snapshotTime: Long): OwnedResourcesSnapshot =
+ OwnedResourcesSnapshot(
+ resourcesMap map {
+ case ((name, instanceId), (value, snapshotTime)) ⇒
+ ResourceInstanceSnapshot(name, instanceId, value, snapshotTime
+ )} toList,
+ snapshotTime
+ )
+}
+
+case class OwnedResourcesSnapshot(data: List[ResourceInstanceSnapshot], snapshotTime: Long)
+ extends UserDataSnapshot[List[ResourceInstanceSnapshot]] with JsonSupport {
+
+ def toResourcesMap: OwnedResourcesMap = {
+ val tuples = for(rc <- data) yield ((rc.resource, rc.instanceId), (rc.instanceAmount, rc.snapshotTime))
+
+ new OwnedResourcesMap(Map(tuples.toSeq: _*))
+ }
+
+ def resourceInstanceSnapshots = data
+
+ def resourceInstanceSnapshotsExcept(resource: String, instanceId: String) = {
+ // Unfortunately, we have to use a List for data, since JSON serialization is not as flexible
+ // (at least out of the box). Thus, the update is O(L), where L is the length of the data List.
+ resourceInstanceSnapshots.filterNot(_.isSameResourceInstance(resource, instanceId))
+ }
+
+ def findResourceInstanceSnapshot(resource: String, instanceId: String): Option[ResourceInstanceSnapshot] = {
+ data.find(x => resource == x.resource && instanceId == x.instanceId)
+ }
+
+ def getResourceInstanceAmount(resource: String, instanceId: String, defaultValue: Double): Double = {
+ findResourceInstanceSnapshot(resource, instanceId).map(_.instanceAmount).getOrElse(defaultValue)
+ }
+
+ def computeResourcesSnapshotUpdate(resource: String, // resource name
+ instanceId: String, // resource instance id
+ newAmount: Double,
+ snapshotTime: Long): (OwnedResourcesSnapshot,
+ Option[ResourceInstanceSnapshot],
+ ResourceInstanceSnapshot) = {
+
+ val newResourceInstance = ResourceInstanceSnapshot(resource, instanceId, newAmount, snapshotTime)
+ val oldResourceInstanceOpt = this.findResourceInstanceSnapshot(resource, instanceId)
+
+ val newResourceInstances = oldResourceInstanceOpt match {
+ case Some(oldResourceInstance) ⇒
+ // Resource instance found, so delete the old one and add the new one
+ newResourceInstance :: resourceInstanceSnapshotsExcept(resource, instanceId)
+ case None ⇒
+ // Resource not found, so this is the first time and we just add the new snapshot
+ newResourceInstance :: resourceInstanceSnapshots
+ }
+
+ val newOwnedResources = OwnedResourcesSnapshot(newResourceInstances, snapshotTime)
+
+ (newOwnedResources, oldResourceInstanceOpt, newResourceInstance)
+ }
+}
+