Fix code assuming that a list always has a tail
[aquarium] / src / main / scala / gr / grnet / aquarium / user / UserDataSnapshot.scala
1 /*
2  * Copyright 2011 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *
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.
16  *
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.
29  *
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.
34  */
35
36 package gr.grnet.aquarium
37 package user
38
39 import gr.grnet.aquarium.util.json.JsonSupport
40 import gr.grnet.aquarium.logic.accounting.Policy
41 import java.util.Date
42 import logic.accounting.dsl.DSLAgreement
43 import com.ckkloverdos.maybe.{Failed, NoVal, Maybe, Just}
44
45 /**
46  * Snapshot of data that are user-related.
47  *
48  * @author Christos KK Loverdos <loverdos@gmail.com>
49  */
50
51 sealed trait UserDataSnapshot[T] extends DataSnapshot[T]
52
53 case class CreditSnapshot(data: Double, snapshotTime: Long) extends UserDataSnapshot[Double]
54
55 case class RolesSnapshot(data: List[String], snapshotTime: Long) extends UserDataSnapshot[List[String]]
56
57 // TODO: Check if needed
58 case class PaymentOrdersSnapshot(data: List[AnyRef], snapshotTime: Long) extends UserDataSnapshot[List[AnyRef]]
59
60 // TODO: Check if needed
61 case class OwnedGroupsSnapshot(data: List[String], snapshotTime: Long) extends UserDataSnapshot[List[String]]
62
63 // TODO: Check if needed
64 case class GroupMembershipsSnapshot(data: List[String], snapshotTime: Long) extends UserDataSnapshot[List[String]]
65
66 /**
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.
69  */
70 case class Agreement(agreement: String, validFrom: Long, validTo: Long) {
71   if (validTo != -1) assert(validTo > validFrom)
72   assert(!agreement.isEmpty)
73
74   Policy.policy(new Date(validFrom)) match {
75     case Just(x) => x.findAgreement(agreement) match {
76       case None => assert(false)
77       case _ =>
78     }
79     case _ => assert(false)
80   }
81 }
82
83 /**
84  * All user agreements. The provided list of agreements cannot have time gaps. This
85  * is checked at object creation type.
86  */
87 case class AgreementSnapshot(data: List[Agreement], snapshotTime: Long)
88   extends UserDataSnapshot[List[Agreement]] {
89
90   ensureNoGaps(data.sortWith((a,b) => if (b.validFrom > a.validFrom) true else false))
91
92   def ensureNoGaps(agreements: List[Agreement]): Unit = agreements match {
93     case ha :: (t @ (hb :: tail)) =>
94       assert(ha.validTo - hb.validFrom == 1);
95       ensureNoGaps(t)
96     case h :: Nil =>
97       assert(h.validTo == -1)
98     case Nil => ()
99   }
100
101   /**
102    * Get the user agreement at the specified timestamp
103    */
104   def getAgreement(at: Long): Maybe[DSLAgreement] =
105     data.find{ x => x.validFrom < at && x.validTo > at} match {
106       case Some(x) => Policy.policy(new Date(at)) match {
107         case Just(y) =>  y.findAgreement(x.agreement) match {
108           case Some(z) => Just(z)
109           case None => NoVal
110         }
111         case NoVal => NoVal
112         case failed @ Failed(x, y) => failed
113       }
114       case None => NoVal
115     }
116 }
117
118 /**
119  * Maintains the current state of a resource instance owned by the user.
120  * The encoding is as follows:
121  *
122  * name: DSLResource.name
123  * instanceId: instance-id (in the resource's descriminatorField)
124  * data: current-resource-value
125  * snapshotTime: last-update-timestamp
126  *
127  * In order to have a uniform representation of the resource state for all
128  * resource types (complex or simple) the following convention applies:
129  *
130  *  - If the resource is complex, the (name, instanceId) is (DSLResource.name, instance-id)
131  *  - If the resource is simple,  the (name, instanceId) is (DSLResource.name, "1")
132  *
133  */
134 case class ResourceInstanceSnapshot(
135     name: String,
136     instanceId: String,
137     data: Double,
138     snapshotTime: Long)
139   extends UserDataSnapshot[Double] {
140
141   def value = data
142   
143   def isSameResource(name: String, instanceId: String) = {
144     this.name == name &&
145     this.instanceId == instanceId
146   }
147 }
148
149 /**
150  * A map from (resourceName, resourceInstanceId) to (value, snapshotTime).
151  * This representation is convenient for computations and updating, while the
152  * [[gr.grnet.aquarium.user.OwnedResourcesSnapshot]] representation is convenient for JSON serialization.
153  */
154 class OwnedResourcesMap(map: Map[(String, String), (Double, Long)]) {
155   def toResourcesSnapshot(snapshotTime: Long): OwnedResourcesSnapshot =
156     OwnedResourcesSnapshot(
157       map map {
158         case ((name, instanceId), (value, snapshotTime)) ⇒
159           ResourceInstanceSnapshot(name, instanceId, value, snapshotTime
160       )} toList,
161       snapshotTime
162     )
163 }
164
165 case class OwnedResourcesSnapshot(data: List[ResourceInstanceSnapshot], snapshotTime: Long)
166   extends UserDataSnapshot[List[ResourceInstanceSnapshot]] with JsonSupport {
167
168   def toResourcesMap: OwnedResourcesMap = {
169     val tuples = for(rc <- data) yield ((rc.name, rc.instanceId), (rc.value, rc.snapshotTime))
170
171     new OwnedResourcesMap(Map(tuples.toSeq: _*))
172   }
173
174   def findResourceSnapshot(name: String, instanceId: String): Option[ResourceInstanceSnapshot] =
175     data.find { x => name == x.name && instanceId == x.instanceId }
176
177   
178   def addOrUpdateResourceSnapshot(name: String,
179                                   instanceId: String,
180                                   newEventValue: Double,
181                                   snapshotTime: Long): (OwnedResourcesSnapshot, Option[ResourceInstanceSnapshot], ResourceInstanceSnapshot) = {
182
183     val newRCInstance = ResourceInstanceSnapshot(name, instanceId, newEventValue, snapshotTime)
184     val oldRCInstanceOpt = this.findResourceSnapshot(name, instanceId)
185     val newData = oldRCInstanceOpt match {
186       case Some(oldRCInstance) ⇒
187         // Need to delete the old one and add the new one
188         // FIXME: Get rid of this Policy.policy
189         val costPolicy = Policy.policy.findResource(name).get.costPolicy
190         val newValue = costPolicy.computeNewResourceInstanceValue(oldRCInstance.value, newRCInstance.value/* =newEventValue */)
191
192         newRCInstance.copy(data = newValue) :: (data.filterNot(_.isSameResource(name, instanceId)))
193       case None ⇒
194         // Resource not found, so this is the first time and we just add the new snapshot
195         newRCInstance :: data
196     }
197
198     val newOwnedResources = this.copy(data = newData, snapshotTime = snapshotTime)
199
200     (newOwnedResources, oldRCInstanceOpt, newRCInstance)
201   }
202 }
203
204
205 /**
206  * A generic exception thrown when errors occur in dealing with user data snapshots
207  *
208  * @author Georgios Gousios <gousiosg@gmail.com>
209  */
210 class UserDataSnapshotException(msg: String) extends Exception(msg)
211
212 /**
213  * Holds the user active/suspended status.
214  *
215  * @author Christos KK Loverdos <loverdos@gmail.com>
216  */
217 case class ActiveSuspendedSnapshot(data: Boolean, snapshotTime: Long) extends UserDataSnapshot[Boolean]