2 * Copyright 2011 GRNET S.A. All rights reserved.
4 * Redistribution and use in source and binary forms, with or
5 * without modification, are permitted provided that the following
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
12 * 2. Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following
14 * disclaimer in the documentation and/or other materials
15 * provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
30 * The views and conclusions contained in the software and
31 * documentation are those of the authors and should not be
32 * interpreted as representing official policies, either expressed
33 * or implied, of GRNET S.A.
36 package gr.grnet.aquarium
39 import gr.grnet.aquarium.util.json.JsonSupport
40 import gr.grnet.aquarium.logic.accounting.Policy
42 import logic.accounting.dsl.DSLAgreement
43 import com.ckkloverdos.maybe.{Failed, NoVal, Maybe, Just}
46 * Snapshot of data that are user-related.
48 * @author Christos KK Loverdos <loverdos@gmail.com>
51 sealed trait UserDataSnapshot[T] extends DataSnapshot[T]
53 case class CreditSnapshot(data: Double, snapshotTime: Long) extends UserDataSnapshot[Double]
55 case class RolesSnapshot(data: List[String], snapshotTime: Long) extends UserDataSnapshot[List[String]]
58 * Represents an agreement valid for a specific amount of time. By convention,
59 * if an agreement is currently valid, then the validTo field is equal to -1.
61 case class Agreement(agreement: String, validFrom: Long, validTo: Long) {
62 if (validTo != -1) assert(validTo > validFrom)
63 assert(!agreement.isEmpty)
65 // Policy.policy(new Date(validFrom)) match {
66 // case Just(x) => x.findAgreement(agreement) match {
67 // case None => assert(false)
70 // case _ => assert(false)
75 * All user agreements. The provided list of agreements cannot have time gaps. This
76 * is checked at object creation type.
78 case class AgreementSnapshot(data: List[Agreement], snapshotTime: Long)
79 extends UserDataSnapshot[List[Agreement]] {
81 ensureNoGaps(data.sortWith((a,b) => if (b.validFrom > a.validFrom) true else false))
83 def ensureNoGaps(agreements: List[Agreement]): Unit = agreements match {
84 case ha :: (t @ (hb :: tail)) =>
85 assert(ha.validTo - hb.validFrom == 1);
88 assert(h.validTo == -1)
93 * Get the user agreement at the specified timestamp
95 def getAgreement(at: Long): Maybe[DSLAgreement] =
96 data.find{ x => x.validFrom < at && x.validTo > at} match {
97 case Some(x) => Policy.policy(new Date(at)) match {
98 case Just(y) => y.findAgreement(x.agreement) match {
99 case Some(z) => Just(z)
103 case failed @ Failed(x, y) => failed
110 * Maintains the current state of a resource instance owned by the user.
111 * The encoding is as follows:
113 * name: DSLResource.name
114 * instanceId: instance-id (in the resource's descriminatorField)
115 * data: current-resource-value
116 * snapshotTime: last-update-timestamp
118 * In order to have a uniform representation of the resource state for all
119 * resource types (complex or simple) the following convention applies:
121 * - If the resource is complex, the (name, instanceId) is (DSLResource.name, instance-id)
122 * - If the resource is simple, the (name, instanceId) is (DSLResource.name, "1")
125 case class ResourceInstanceSnapshot(/**
126 * Same as `resource` of [[gr.grnet.aquarium.logic.events.ResourceEvent]]
131 * Same as `instanceId` of [[gr.grnet.aquarium.logic.events.ResourceEvent]]
134 data: Double, // FIXME: data is a missleading name here
136 extends UserDataSnapshot[Double] {
139 * This is the amount kept for the resource instance.
141 * The general rule is that an amount saved in a [[gr.grnet.aquarium.user.ResourceInstanceSnapshot]] represents a
142 * total value, while a value appearing in a [[gr.grnet.aquarium.logic.events.ResourceEvent]] represents a
143 * difference. How these two values are combined to form the new amount is dictated by the underlying
144 * [[gr.grnet.aquarium.logic.accounting.dsl.DSLCostPolicy]]
146 def instanceAmount = data
148 def isSameResourceInstance(resource: String, instanceId: String) = {
149 this.resource == resource &&
150 this.instanceId == instanceId
155 * A map from (resourceName, resourceInstanceId) to (value, snapshotTime).
156 * This representation is convenient for computations and updating, while the
157 * [[gr.grnet.aquarium.user.OwnedResourcesSnapshot]] representation is convenient for JSON serialization.
159 class OwnedResourcesMap(resourcesMap: Map[(String, String), (Double, Long)]) {
160 def toResourcesSnapshot(snapshotTime: Long): OwnedResourcesSnapshot =
161 OwnedResourcesSnapshot(
163 case ((name, instanceId), (value, snapshotTime)) ⇒
164 ResourceInstanceSnapshot(name, instanceId, value, snapshotTime
170 case class OwnedResourcesSnapshot(data: List[ResourceInstanceSnapshot], snapshotTime: Long)
171 extends UserDataSnapshot[List[ResourceInstanceSnapshot]] with JsonSupport {
173 def toResourcesMap: OwnedResourcesMap = {
174 val tuples = for(rc <- data) yield ((rc.resource, rc.instanceId), (rc.instanceAmount, rc.snapshotTime))
176 new OwnedResourcesMap(Map(tuples.toSeq: _*))
179 def resourceInstanceSnapshots = data
181 def resourceInstanceSnapshotsExcept(resource: String, instanceId: String) = {
182 // Unfortunately, we have to use a List for data, since JSON serialization is not as flexible
183 // (at least out of the box). Thus, the update is O(L), where L is the length of the data List.
184 resourceInstanceSnapshots.filterNot(_.isSameResourceInstance(resource, instanceId))
187 def findResourceInstanceSnapshot(resource: String, instanceId: String): Option[ResourceInstanceSnapshot] = {
188 data.find(x => resource == x.resource && instanceId == x.instanceId)
191 def getResourceInstanceAmount(resource: String, instanceId: String, defaultValue: Double): Double = {
192 findResourceInstanceSnapshot(resource, instanceId).map(_.instanceAmount).getOrElse(defaultValue)
195 def computeResourcesSnapshotUpdate(resource: String, // resource name
196 instanceId: String, // resource instance id
198 snapshotTime: Long): (OwnedResourcesSnapshot,
199 Option[ResourceInstanceSnapshot],
200 ResourceInstanceSnapshot) = {
202 val newResourceInstance = ResourceInstanceSnapshot(resource, instanceId, newAmount, snapshotTime)
203 val oldResourceInstanceOpt = this.findResourceInstanceSnapshot(resource, instanceId)
205 val newResourceInstances = oldResourceInstanceOpt match {
206 case Some(oldResourceInstance) ⇒
207 // Resource instance found, so delete the old one and add the new one
208 newResourceInstance :: resourceInstanceSnapshotsExcept(resource, instanceId)
210 // Resource not found, so this is the first time and we just add the new snapshot
211 newResourceInstance :: resourceInstanceSnapshots
214 val newOwnedResources = OwnedResourcesSnapshot(newResourceInstances, snapshotTime)
216 (newOwnedResources, oldResourceInstanceOpt, newResourceInstance)
222 * A generic exception thrown when errors occur in dealing with user data snapshots
224 * @author Georgios Gousios <gousiosg@gmail.com>
226 class UserDataSnapshotException(msg: String) extends Exception(msg)
229 * Holds the user active/suspended status.
231 * @author Christos KK Loverdos <loverdos@gmail.com>
233 case class ActiveSuspendedSnapshot(data: Boolean, snapshotTime: Long) extends UserDataSnapshot[Boolean]