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]]
57 // TODO: Check if needed
58 case class PaymentOrdersSnapshot(data: List[AnyRef], snapshotTime: Long) extends UserDataSnapshot[List[AnyRef]]
60 // TODO: Check if needed
61 case class OwnedGroupsSnapshot(data: List[String], snapshotTime: Long) extends UserDataSnapshot[List[String]]
63 // TODO: Check if needed
64 case class GroupMembershipsSnapshot(data: List[String], snapshotTime: Long) extends UserDataSnapshot[List[String]]
67 * Represents an agreement valid for a specific amount of time. By convention,
68 * if an agreement is currently valid, then the validTo field is equal to -1.
70 case class Agreement(agreement: String, validFrom: Long, validTo: Long) {
71 if (validTo != -1) assert(validTo > validFrom)
72 assert(!agreement.isEmpty)
74 Policy.policy(new Date(validFrom)) match {
75 case Just(x) => x.findAgreement(agreement) match {
76 case None => assert(false)
79 case _ => assert(false)
84 * All user agreements. The provided list of agreements cannot have time gaps. This
85 * is checked at object creation type.
87 case class AgreementSnapshot(data: List[Agreement], snapshotTime: Long)
88 extends UserDataSnapshot[List[Agreement]] {
90 ensureNoGaps(data.sortWith((a,b) => if (b.validFrom > a.validFrom) true else false))
92 def ensureNoGaps(agreements: List[Agreement]): Unit = agreements match {
93 case h :: t => assert(h.validTo - t.head.validFrom == 1); ensureNoGaps(t)
94 case h :: Nil => assert(h.validTo == -1)
98 * Get the user agreement at the specified timestamp
100 def getAgreement(at: Long): Maybe[DSLAgreement] =
101 data.find{ x => x.validFrom < at && x.validTo > at} match {
102 case Some(x) => Policy.policy(new Date(at)) match {
103 case Just(y) => y.findAgreement(x.agreement) match {
104 case Some(z) => Just(z)
108 case failed @ Failed(x, y) => failed
115 * Maintains the current state of a resource instance owned by the user.
116 * The encoding is as follows:
118 * name: DSLResource.name
119 * instanceId: instance-id (in the resource's descriminatorField)
120 * data: current-resource-value
121 * snapshotTime: last-update-timestamp
123 * In order to have a uniform representation of the resource state for all
124 * resource types (complex or simple) the following convention applies:
126 * - If the resource is complex, the (name, instanceId) is (DSLResource.name, instance-id)
127 * - If the resource is simple, the (name, instanceId) is (DSLResource.name, "1")
130 case class ResourceInstanceSnapshot(
135 extends UserDataSnapshot[Double] {
138 * This is the amount kept for the resource instance.
140 * The general rule is that an amount saved in a [[gr.grnet.aquarium.user.ResourceInstanceSnapshot]] represents a
141 * total value, while a value appearing in a [[gr.grnet.aquarium.logic.events.ResourceEvent]] represents a
142 * difference. How these two values are combined to form the new amount is dictated by the underlying
143 * [[gr.grnet.aquarium.logic.accounting.dsl.DSLCostPolicy]]
145 def instanceAmount = data
147 def isSameResource(name: String, instanceId: String) = {
149 this.instanceId == instanceId
154 * A map from (resourceName, resourceInstanceId) to (value, snapshotTime).
155 * This representation is convenient for computations and updating, while the
156 * [[gr.grnet.aquarium.user.OwnedResourcesSnapshot]] representation is convenient for JSON serialization.
158 class OwnedResourcesMap(map: Map[(String, String), (Double, Long)]) {
159 def toResourcesSnapshot(snapshotTime: Long): OwnedResourcesSnapshot =
160 OwnedResourcesSnapshot(
162 case ((name, instanceId), (value, snapshotTime)) ⇒
163 ResourceInstanceSnapshot(name, instanceId, value, snapshotTime
169 case class OwnedResourcesSnapshot(data: List[ResourceInstanceSnapshot], snapshotTime: Long)
170 extends UserDataSnapshot[List[ResourceInstanceSnapshot]] with JsonSupport {
172 def toResourcesMap: OwnedResourcesMap = {
173 val tuples = for(rc <- data) yield ((rc.name, rc.instanceId), (rc.instanceAmount, rc.snapshotTime))
175 new OwnedResourcesMap(Map(tuples.toSeq: _*))
178 def findResourceSnapshot(name: String, instanceId: String): Option[ResourceInstanceSnapshot] =
179 data.find { x => name == x.name && instanceId == x.instanceId }
181 def addOrUpdateResourceSnapshot(name: String, // resource name
182 instanceId: String, // resource instance id
183 newRCInstanceAmount: Double,
184 snapshotTime: Long): (OwnedResourcesSnapshot, Option[ResourceInstanceSnapshot], ResourceInstanceSnapshot) = {
186 val newRCInstance = ResourceInstanceSnapshot(name, instanceId, newRCInstanceAmount, snapshotTime)
187 val oldRCInstanceOpt = this.findResourceSnapshot(name, instanceId)
189 val newData = oldRCInstanceOpt match {
190 case Some(oldRCInstance) ⇒
191 // Resource instance found, so delete the old one and add the new one
192 newRCInstance :: (data.filterNot(_.isSameResource(name, instanceId)))
194 // Resource not found, so this is the first time and we just add the new snapshot
195 newRCInstance :: data
198 val newOwnedResources = this.copy(data = newData, snapshotTime = snapshotTime)
200 (newOwnedResources, oldRCInstanceOpt, newRCInstance)
206 * A generic exception thrown when errors occur in dealing with user data snapshots
208 * @author Georgios Gousios <gousiosg@gmail.com>
210 class UserDataSnapshotException(msg: String) extends Exception(msg)
213 * Holds the user active/suspended status.
215 * @author Christos KK Loverdos <loverdos@gmail.com>
217 case class ActiveSuspendedSnapshot(data: Boolean, snapshotTime: Long) extends UserDataSnapshot[Boolean]