2 * Copyright 2011-2012 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.{findFromMapAsMaybe, findAndRemoveFromMap, shortClassNameOf}
40 import gr.grnet.aquarium.logic.accounting.Policy
42 import com.ckkloverdos.maybe.{NoVal, Maybe, Just}
43 import gr.grnet.aquarium.event.resource.ResourceEventModel.FullMutableResourceTypeMap
44 import logic.accounting.dsl.{Timeslot, DSLAgreement}
45 import collection.immutable.{TreeMap, SortedMap}
46 import util.date.MutableDateCalc
47 import event.resource.ResourceEventModel
50 * Snapshot of data that are user-related.
52 * @author Christos KK Loverdos <loverdos@gmail.com>
55 case class CreditSnapshot(creditAmount: Double, snapshotTime: Long) extends DataSnapshot
57 case class RolesSnapshot(roles: List[String], snapshotTime: Long) extends DataSnapshot
60 * Represents an agreement valid for a specific amount of time. By convention,
61 * if an agreement is currently valid, then the validTo field is equal to `Long.MaxValue`.
63 case class Agreement(name: String, validFrom: Long, validTo: Long = Long.MaxValue) {
64 assert(validTo > validFrom)
67 // Policy.policy(new Date(validFrom)) match {
68 // case Just(x) => x.findAgreement(agreement) match {
69 // case None => assert(false)
72 // case _ => assert(false)
75 def timeslot = Timeslot(new Date(validFrom), new Date(validTo))
77 override def toString =
78 "Agreement(%s, %s, %s)".
79 format(name, new MutableDateCalc(validFrom), new MutableDateCalc(validTo))
83 * All user agreements. The provided list of agreements cannot have time gaps. This
84 * is checked at object creation type.
86 case class AgreementSnapshot(agreements: List[Agreement], snapshotTime: Long) extends DataSnapshot {
88 ensureNoGaps(agreements.sortWith((a,b) => if (b.validFrom > a.validFrom) true else false))
90 def agreementsByTimeslot: SortedMap[Timeslot, String] = {
91 TreeMap(agreements.map(ag => (ag.timeslot, ag.name)): _*)
94 def ensureNoGaps(agreements: List[Agreement]): Unit = agreements match {
95 case ha :: (t @ (hb :: tail)) =>
96 assert(ha.validTo - hb.validFrom == 1);
99 assert(h.validTo == Long.MaxValue)
104 * Get the user agreement at the specified timestamp
106 def getAgreement(at: Long): Maybe[DSLAgreement] =
107 agreements.find{ x => x.validFrom < at && x.validTo > at} match {
108 case Some(x) => Policy.policy(new Date(at)).findAgreement(x.name) match {
109 case Some(z) => Just(z)
115 override def toString = {
116 "%s(%s, %s)".format(shortClassNameOf(this), agreements, new MutableDateCalc(snapshotTime).toString)
121 * Maintains the current state of a resource instance owned by the user.
122 * The encoding is as follows:
124 * name: DSLResource.name
125 * instanceId: instance-id (in the resource's descriminatorField)
126 * data: current-resource-value
127 * snapshotTime: last-update-timestamp
129 * In order to have a uniform representation of the resource state for all
130 * resource types (complex or simple) the following convention applies:
132 * - If the resource is complex, the (name, instanceId) is (DSLResource.name, instance-id)
133 * - If the resource is simple, the (name, instanceId) is (DSLResource.name, "1")
135 * @param resource Same as `resource` of [[gr.grnet.aquarium.event.ResourceEvent]]
136 * @param instanceId Same as `instanceId` of [[gr.grnet.aquarium.event.ResourceEvent]]
137 * @param instanceAmount This is the amount kept for the resource instance.
138 * The general rule is that an amount saved in a [[gr.grnet.aquarium.user.ResourceInstanceSnapshot]]
139 * represents a total value, while a value appearing in a [[gr.grnet.aquarium.event.ResourceEvent]]
140 * represents a difference. How these two values are combined to form the new amount is dictated
141 * by the underlying [[gr.grnet.aquarium.logic.accounting.dsl.DSLCostPolicy]]
142 * @param snapshotTime
144 * @author Christos KK Loverdos <loverdos@gmail.com>
146 case class ResourceInstanceSnapshot(resource: String,
148 instanceAmount: Double,
149 snapshotTime: Long) extends DataSnapshot {
151 def isSameResourceInstance(resource: String, instanceId: String) = {
152 this.resource == resource &&
153 this.instanceId == instanceId
158 * A map from (resourceName, resourceInstanceId) to (value, snapshotTime).
159 * This representation is convenient for computations and updating, while the
160 * [[gr.grnet.aquarium.user.OwnedResourcesSnapshot]] representation is convenient for JSON serialization.
162 * @author Christos KK Loverdos <loverdos@gmail.com>
164 class OwnedResourcesMap(resourcesMap: Map[(String, String), (Double, Long)]) {
165 def toResourcesSnapshot(snapshotTime: Long): OwnedResourcesSnapshot =
166 OwnedResourcesSnapshot(
168 case ((name, instanceId), (value, snapshotTime)) ⇒
169 ResourceInstanceSnapshot(name, instanceId, value, snapshotTime
177 * @param resourceInstanceSnapshots
178 * @param snapshotTime
180 * @author Christos KK Loverdos <loverdos@gmail.com>
182 case class OwnedResourcesSnapshot(resourceInstanceSnapshots: List[ResourceInstanceSnapshot], snapshotTime: Long)
183 extends DataSnapshot {
185 def toResourcesMap: OwnedResourcesMap = {
186 val tuples = for(rc <- resourceInstanceSnapshots) yield ((rc.resource, rc.instanceId), (rc.instanceAmount, rc.snapshotTime))
188 new OwnedResourcesMap(Map(tuples.toSeq: _*))
191 def resourceInstanceSnapshotsExcept(resource: String, instanceId: String) = {
192 // Unfortunately, we have to use a List for data, since JSON serialization is not as flexible
193 // (at least out of the box). Thus, the update is O(L), where L is the length of the data List.
194 resourceInstanceSnapshots.filterNot(_.isSameResourceInstance(resource, instanceId))
197 def findResourceInstanceSnapshot(resource: String, instanceId: String): Option[ResourceInstanceSnapshot] = {
198 resourceInstanceSnapshots.find(x => resource == x.resource && instanceId == x.instanceId)
201 def getResourceInstanceAmount(resource: String, instanceId: String, defaultValue: Double): Double = {
202 findResourceInstanceSnapshot(resource, instanceId).map(_.instanceAmount).getOrElse(defaultValue)
205 def computeResourcesSnapshotUpdate(resource: String, // resource name
206 instanceId: String, // resource instance id
208 snapshotTime: Long): (OwnedResourcesSnapshot,
209 Option[ResourceInstanceSnapshot],
210 ResourceInstanceSnapshot) = {
212 val newResourceInstance = ResourceInstanceSnapshot(resource, instanceId, newAmount, snapshotTime)
213 val oldResourceInstanceOpt = this.findResourceInstanceSnapshot(resource, instanceId)
215 val newResourceInstances = oldResourceInstanceOpt match {
216 case Some(oldResourceInstance) ⇒
217 // Resource instance found, so delete the old one and add the new one
218 newResourceInstance :: resourceInstanceSnapshotsExcept(resource, instanceId)
220 // Resource not found, so this is the first time and we just add the new snapshot
221 newResourceInstance :: resourceInstanceSnapshots
224 val newOwnedResources = OwnedResourcesSnapshot(newResourceInstances, snapshotTime)
226 (newOwnedResources, oldResourceInstanceOpt, newResourceInstance)
232 * A generic exception thrown when errors occur in dealing with user data snapshots
234 * @author Georgios Gousios <gousiosg@gmail.com>
236 class DataSnapshotException(msg: String) extends Exception(msg)
239 * Holds the user active/suspended status.
241 * @author Christos KK Loverdos <loverdos@gmail.com>
243 case class ActiveStateSnapshot(isActive: Boolean, snapshotTime: Long) extends DataSnapshot
246 * Keeps the latest resource event per resource instance.
248 * @param resourceEvents
249 * @param snapshotTime
251 * @author Christos KK Loverdos <loverdos@gmail.com>
253 case class LatestResourceEventsSnapshot(resourceEvents: List[ResourceEventModel],
254 snapshotTime: Long) extends DataSnapshot {
257 * The gateway to playing with mutable state.
259 * @return A fresh instance of [[gr.grnet.aquarium.user.LatestResourceEventsWorker]].
261 def toMutableWorker = {
262 val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]()
263 for(latestEvent <- resourceEvents) {
264 map(latestEvent.fullResourceInfo) = latestEvent
266 LatestResourceEventsWorker(map)
271 * This is the mutable cousin of [[gr.grnet.aquarium.user.LatestResourceEventsSnapshot]].
273 * @param latestEventsMap
275 * @author Christos KK Loverdos <loverdos@gmail.com>
277 case class LatestResourceEventsWorker(latestEventsMap: FullMutableResourceTypeMap) {
280 * The gateway to immutable state.
282 * @param snapshotTime The relevant snapshot time.
283 * @return A fresh instance of [[gr.grnet.aquarium.user.LatestResourceEventsSnapshot]].
285 def toImmutableSnapshot(snapshotTime: Long) =
286 LatestResourceEventsSnapshot(latestEventsMap.valuesIterator.toList, snapshotTime)
288 def updateResourceEvent(resourceEvent: ResourceEventModel): Unit = {
289 latestEventsMap((resourceEvent.resource, resourceEvent.instanceID)) = resourceEvent
292 def findResourceEvent(resource: String, instanceId: String): Maybe[ResourceEventModel] = {
293 findFromMapAsMaybe(latestEventsMap, (resource, instanceId))
296 def findAndRemoveResourceEvent(resource: String, instanceId: String): Maybe[ResourceEventModel] = {
297 findAndRemoveFromMap(latestEventsMap, (resource, instanceId))
300 def size = latestEventsMap.size
302 def foreach[U](f: ResourceEventModel => U): Unit = {
303 latestEventsMap.valuesIterator.foreach(f)
307 object LatestResourceEventsWorker {
308 final val Empty = LatestResourceEventsWorker(scala.collection.mutable.Map())
311 * Helper factory to construct a worker from a list of events.
313 def fromList(latestEventsList: List[ResourceEventModel]): LatestResourceEventsWorker = {
314 LatestResourceEventsSnapshot(latestEventsList, 0L).toMutableWorker
319 * Keeps the implicit OFF events when a billing period ends.
320 * This is normally recorded in the [[gr.grnet.aquarium.user.UserState]].
322 * @param implicitlyIssuedEvents
323 * @param snapshotTime
325 * @author Christos KK Loverdos <loverdos@gmail.com>
327 case class ImplicitlyIssuedResourceEventsSnapshot(implicitlyIssuedEvents: List[ResourceEventModel],
328 snapshotTime: Long) extends DataSnapshot {
330 * The gateway to playing with mutable state.
332 * @return A fresh instance of [[gr.grnet.aquarium.user.ImplicitlyIssuedResourceEventsWorker]].
334 def toMutableWorker = {
335 val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]()
336 for(implicitEvent <- implicitlyIssuedEvents) {
337 map(implicitEvent.fullResourceInfo) = implicitEvent
340 ImplicitlyIssuedResourceEventsWorker(map)
345 * This is the mutable cousin of [[gr.grnet.aquarium.user.ImplicitlyIssuedResourceEventsSnapshot]].
347 * @param implicitlyIssuedEventsMap
349 * @author Christos KK Loverdos <loverdos@gmail.com>
351 case class ImplicitlyIssuedResourceEventsWorker(implicitlyIssuedEventsMap: FullMutableResourceTypeMap) {
353 def toList: scala.List[ResourceEventModel] = {
354 implicitlyIssuedEventsMap.valuesIterator.toList
357 def toImmutableSnapshot(snapshotTime: Long) =
358 ImplicitlyIssuedResourceEventsSnapshot(toList, snapshotTime)
360 def findAndRemoveResourceEvent(resource: String, instanceId: String): Maybe[ResourceEventModel] = {
361 findAndRemoveFromMap(implicitlyIssuedEventsMap, (resource, instanceId))
364 def size = implicitlyIssuedEventsMap.size
366 def foreach[U](f: ResourceEventModel => U): Unit = {
367 implicitlyIssuedEventsMap.valuesIterator.foreach(f)
371 object ImplicitlyIssuedResourceEventsWorker {
372 final val Empty = ImplicitlyIssuedResourceEventsWorker(scala.collection.mutable.Map())
377 * @author Christos KK Loverdos <loverdos@gmail.com>
379 * @param ignoredFirstEvents
380 * @param snapshotTime
382 case class IgnoredFirstResourceEventsSnapshot(ignoredFirstEvents: List[ResourceEventModel],
383 snapshotTime: Long) extends DataSnapshot {
384 def toMutableWorker = {
385 val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]()
386 for(ignoredFirstEvent <- ignoredFirstEvents) {
387 map(ignoredFirstEvent.fullResourceInfo) = ignoredFirstEvent
390 IgnoredFirstResourceEventsWorker(map)
396 * @author Christos KK Loverdos <loverdos@gmail.com>
397 * @param ignoredFirstEventsMap
399 case class IgnoredFirstResourceEventsWorker(ignoredFirstEventsMap: FullMutableResourceTypeMap) {
400 def toImmutableSnapshot(snapshotTime: Long) =
401 IgnoredFirstResourceEventsSnapshot(ignoredFirstEventsMap.valuesIterator.toList, snapshotTime)
403 def findAndRemoveResourceEvent(resource: String, instanceId: String): Maybe[ResourceEventModel] = {
404 findAndRemoveFromMap(ignoredFirstEventsMap, (resource, instanceId))
407 def updateResourceEvent(resourceEvent: ResourceEventModel): Unit = {
408 ignoredFirstEventsMap((resourceEvent.resource, resourceEvent.instanceID)) = resourceEvent
411 def size = ignoredFirstEventsMap.size
413 def foreach[U](f: ResourceEventModel => U): Unit = {
414 ignoredFirstEventsMap.valuesIterator.foreach(f)
418 object IgnoredFirstResourceEventsWorker {
419 final val Empty = IgnoredFirstResourceEventsWorker(scala.collection.mutable.Map())