import gr.grnet.aquarium.util.json.JsonSupport
import gr.grnet.aquarium.logic.accounting.Policy
+import java.util.Date
+import logic.accounting.dsl.DSLAgreement
+import com.ckkloverdos.maybe.{Failed, NoVal, Maybe, Just}
/**
* Snapshot of data that are user-related.
case class CreditSnapshot(data: Double, snapshotTime: Long) extends UserDataSnapshot[Double]
-case class AgreementSnapshot(data: String, snapshotTime: Long) extends UserDataSnapshot[String]
-
case class RolesSnapshot(data: List[String], snapshotTime: Long) extends UserDataSnapshot[List[String]]
-// TODO: Check if needed
-case class PaymentOrdersSnapshot(data: List[AnyRef], snapshotTime: Long) extends UserDataSnapshot[List[AnyRef]]
+/**
+ * Represents an agreement valid for a specific amount of time. By convention,
+ * if an agreement is currently valid, then the validTo field is equal to -1.
+ */
+case class Agreement(agreement: String, validFrom: Long, validTo: Long) {
+ if (validTo != -1) assert(validTo > validFrom)
+ assert(!agreement.isEmpty)
+
+// Policy.policy(new Date(validFrom)) match {
+// case Just(x) => x.findAgreement(agreement) match {
+// case None => assert(false)
+// case _ =>
+// }
+// case _ => assert(false)
+// }
+}
-// TODO: Check if needed
-case class OwnedGroupsSnapshot(data: List[String], snapshotTime: Long) extends UserDataSnapshot[List[String]]
+/**
+ * All user agreements. The provided list of agreements cannot have time gaps. This
+ * is checked at object creation type.
+ */
+case class AgreementSnapshot(data: List[Agreement], snapshotTime: Long)
+ extends UserDataSnapshot[List[Agreement]] {
+
+ ensureNoGaps(data.sortWith((a,b) => if (b.validFrom > a.validFrom) true else false))
+
+ def ensureNoGaps(agreements: List[Agreement]): Unit = agreements match {
+ case ha :: (t @ (hb :: tail)) =>
+ assert(ha.validTo - hb.validFrom == 1);
+ ensureNoGaps(t)
+ case h :: Nil =>
+ assert(h.validTo == -1)
+ case Nil => ()
+ }
-// TODO: Check if needed
-case class GroupMembershipsSnapshot(data: List[String], snapshotTime: Long) extends UserDataSnapshot[List[String]]
+ /**
+ * Get the user agreement at the specified timestamp
+ */
+ def getAgreement(at: Long): Maybe[DSLAgreement] =
+ data.find{ x => x.validFrom < at && x.validTo > at} match {
+ case Some(x) => Policy.policy(new Date(at)) match {
+ case Just(y) => y.findAgreement(x.agreement) match {
+ case Some(z) => Just(z)
+ case None => NoVal
+ }
+ case NoVal => NoVal
+ case failed @ Failed(x, y) => failed
+ }
+ case None => NoVal
+ }
+}
/**
* Maintains the current state of a resource instance owned by the user.
* - If the resource is simple, the (name, instanceId) is (DSLResource.name, "1")
*
*/
-case class ResourceInstanceSnapshot(
- name: String,
- instanceId: String,
- data: Double,
- snapshotTime: Long)
+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] {
- def value = data
+ /**
+ * 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 isSameResource(name: String, instanceId: String) = {
- this.name == name &&
+ def isSameResourceInstance(resource: String, instanceId: String) = {
+ this.resource == resource &&
this.instanceId == instanceId
}
}
* This representation is convenient for computations and updating, while the
* [[gr.grnet.aquarium.user.OwnedResourcesSnapshot]] representation is convenient for JSON serialization.
*/
-class OwnedResourcesMap(map: Map[(String, String), (Double, Long)]) {
+class OwnedResourcesMap(resourcesMap: Map[(String, String), (Double, Long)]) {
def toResourcesSnapshot(snapshotTime: Long): OwnedResourcesSnapshot =
OwnedResourcesSnapshot(
- map map {
+ resourcesMap map {
case ((name, instanceId), (value, snapshotTime)) ⇒
ResourceInstanceSnapshot(name, instanceId, value, snapshotTime
)} toList,
extends UserDataSnapshot[List[ResourceInstanceSnapshot]] with JsonSupport {
def toResourcesMap: OwnedResourcesMap = {
- val tuples = for(rc <- data) yield ((rc.name, rc.instanceId), (rc.value, rc.snapshotTime))
+ val tuples = for(rc <- data) yield ((rc.resource, rc.instanceId), (rc.instanceAmount, rc.snapshotTime))
new OwnedResourcesMap(Map(tuples.toSeq: _*))
}
- def findResourceSnapshot(name: String, instanceId: String): Option[ResourceInstanceSnapshot] =
- data.find { x => name == x.name && instanceId == x.instanceId }
+ def resourceInstanceSnapshots = data
-
- def addOrUpdateResourceSnapshot(name: String,
- instanceId: String,
- newEventValue: Double,
- snapshotTime: Long): (OwnedResourcesSnapshot, Option[ResourceInstanceSnapshot], ResourceInstanceSnapshot) = {
-
- val newRCInstance = ResourceInstanceSnapshot(name, instanceId, newEventValue, snapshotTime)
- val oldRCInstanceOpt = this.findResourceSnapshot(name, instanceId)
- val newData = oldRCInstanceOpt match {
- case Some(oldRCInstance) ⇒
- // Need to delete the old one and add the new one
- // FIXME: Get rid of this Policy.policy
- val costPolicy = Policy.policy.findResource(name).get.costPolicy
- val newValue = costPolicy.computeNewResourceInstanceValue(oldRCInstance.value, newRCInstance.value/* =newEventValue */)
-
- newRCInstance.copy(data = newValue) :: (data.filterNot(_.isSameResource(name, instanceId)))
+ 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
- newRCInstance :: data
+ newResourceInstance :: resourceInstanceSnapshots
}
- val newOwnedResources = this.copy(data = newData, snapshotTime = snapshotTime)
+ val newOwnedResources = OwnedResourcesSnapshot(newResourceInstances, snapshotTime)
- (newOwnedResources, oldRCInstanceOpt, newRCInstance)
- }
+ (newOwnedResources, oldResourceInstanceOpt, newResourceInstance)
+ }
}