X-Git-Url: https://code.grnet.gr/git/aquarium/blobdiff_plain/5473142c3ed917d7e8714f6b942cb7ed032a8bee..28bb45ec3fbedac2aa44f1691676dea8ee323534:/src/main/scala/gr/grnet/aquarium/user/UserDataSnapshot.scala diff --git a/src/main/scala/gr/grnet/aquarium/user/UserDataSnapshot.scala b/src/main/scala/gr/grnet/aquarium/user/UserDataSnapshot.scala index 4384cee..c9a7706 100644 --- a/src/main/scala/gr/grnet/aquarium/user/UserDataSnapshot.scala +++ b/src/main/scala/gr/grnet/aquarium/user/UserDataSnapshot.scala @@ -36,8 +36,11 @@ package gr.grnet.aquarium package user -import logic.accounting.dsl.{DSLResource, DSLAgreement} - +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. @@ -49,14 +52,182 @@ sealed trait UserDataSnapshot[T] extends DataSnapshot[T] case class CreditSnapshot(data: Double, snapshotTime: Long) extends UserDataSnapshot[Double] -case class AgreementSnapshot(data: DSLAgreement, snapshotTime: Long) extends UserDataSnapshot[DSLAgreement] - case class RolesSnapshot(data: List[String], snapshotTime: Long) extends UserDataSnapshot[List[String]] -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) +// } +} + +/** + * 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 => () + } + + /** + * 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. + * The encoding is as follows: + * + * name: DSLResource.name + * instanceId: instance-id (in the resource's descriminatorField) + * data: current-resource-value + * snapshotTime: last-update-timestamp + * + * In order to have a uniform representation of the resource state for all + * resource types (complex or simple) the following convention applies: + * + * - If the resource is complex, the (name, instanceId) is (DSLResource.name, instance-id) + * - If the resource is simple, the (name, instanceId) is (DSLResource.name, "1") + * + */ +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) -case class OwnedGroupsSnapshot(data: List[String], snapshotTime: Long) extends UserDataSnapshot[List[String]] + 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 + } -case class GroupMembershipsSnapshot(data: List[String], snapshotTime: Long) extends UserDataSnapshot[List[String]] + val newOwnedResources = OwnedResourcesSnapshot(newResourceInstances, snapshotTime) -case class OwnedResourcesSnapshot(data: Map[DSLResource, Any /*ResourceState*/], snapshotTime: Long) extends UserDataSnapshot[Map[DSLResource, Any /*ResourceState*/]] + (newOwnedResources, oldResourceInstanceOpt, newResourceInstance) + } +} + + +/** + * A generic exception thrown when errors occur in dealing with user data snapshots + * + * @author Georgios Gousios + */ +class UserDataSnapshotException(msg: String) extends Exception(msg) + +/** + * Holds the user active/suspended status. + * + * @author Christos KK Loverdos + */ +case class ActiveSuspendedSnapshot(data: Boolean, snapshotTime: Long) extends UserDataSnapshot[Boolean]