Test case for new policy configuration
[aquarium] / src / main / scala / gr / grnet / aquarium / store / memory / MemStoreProvider.scala
1 /*
2  * Copyright 2011-2012 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.store.memory
37
38 import com.ckkloverdos.props.Props
39 import com.ckkloverdos.maybe.Just
40 import gr.grnet.aquarium.store._
41 import scala.collection.JavaConversions._
42 import collection.mutable.ConcurrentMap
43 import java.util.concurrent.ConcurrentHashMap
44 import gr.grnet.aquarium.Configurable
45 import gr.grnet.aquarium.event.model.im.{StdIMEvent, IMEventModel}
46 import org.bson.types.ObjectId
47 import gr.grnet.aquarium.event.model.resource.{StdResourceEvent, ResourceEventModel}
48 import gr.grnet.aquarium.computation.state.UserState
49 import gr.grnet.aquarium.util.Tags
50 import gr.grnet.aquarium.computation.BillingMonthInfo
51 import gr.grnet.aquarium.policy.{PolicyModel, StdPolicy}
52
53 /**
54  * An implementation of various stores that persists parts in memory.
55  *
56  * This is just for testing purposes.
57  * 
58  * @author Christos KK Loverdos <loverdos@gmail.com>
59  * @author Georgios Gousios <gousiosg@gmail.com>
60  */
61
62 class MemStoreProvider
63 extends StoreProvider
64    with UserStateStore
65    with Configurable
66    with PolicyStore
67    with ResourceEventStore
68    with IMEventStore {
69
70   override type IMEvent = MemIMEvent
71   override type ResourceEvent = MemResourceEvent
72   override type Policy = StdPolicy
73
74   private[this] var _userStates = List[UserState]()
75   private[this] var _policies  = List[Policy]()
76   private[this] var _resourceEvents = List[ResourceEvent]()
77
78   private[this] val imEventById: ConcurrentMap[String, MemIMEvent] = new ConcurrentHashMap[String, MemIMEvent]()
79
80
81   def propertyPrefix = None
82
83   def configure(props: Props) = {
84   }
85
86   override def toString = {
87     val map = Map(
88       Tags.UserStateTag     -> _userStates.size,
89       Tags.ResourceEventTag -> _resourceEvents.size,
90       Tags.IMEventTag       -> imEventById.size,
91       "PolicyEntry"         -> _policies.size
92     )
93
94     "MemStoreProvider(%s)" format map
95   }
96
97   //+ StoreProvider
98   def userStateStore = this
99
100   def resourceEventStore = this
101
102   def imEventStore = this
103
104   def policyStore = this
105   //- StoreProvider
106
107
108   //+ UserStateStore
109   def insertUserState(userState: UserState): UserState = {
110     _userStates = userState.copy(_id = new ObjectId().toString) :: _userStates
111     userState
112   }
113
114   def findUserStateByUserID(userID: String) = {
115     _userStates.find(_.userID == userID)
116   }
117
118   def findLatestUserStateForFullMonthBilling(userID: String, bmi: BillingMonthInfo): Option[UserState] = {
119     val goodOnes = _userStates.filter(_.theFullBillingMonth.isDefined).filter { userState ⇒
120         val f1 = userState.userID == userID
121         val f2 = userState.isFullBillingMonthState
122         val bm = userState.theFullBillingMonth.get
123         val f3 = bm == bmi
124
125         f1 && f2 && f3
126     }
127     
128     goodOnes.sortWith {
129       case (us1, us2) ⇒
130         us1.occurredMillis > us2.occurredMillis
131     } match {
132       case head :: _ ⇒
133         Some(head)
134       case _ ⇒
135         None
136     }
137   }
138   //- UserStateStore
139
140   //+ ResourceEventStore
141   def createResourceEventFromOther(event: ResourceEventModel): ResourceEvent = {
142     if(event.isInstanceOf[MemResourceEvent]) event.asInstanceOf[MemResourceEvent]
143     else {
144       import event._
145       new StdResourceEvent(
146         id,
147         occurredMillis,
148         receivedMillis,
149         userID,
150         clientID,
151         resource,
152         instanceID,
153         value,
154         eventVersion,
155         details
156       ): MemResourceEvent
157     }
158   }
159
160   override def clearResourceEvents() = {
161     _resourceEvents = Nil
162   }
163
164   def pingResourceEventStore(): Unit = {
165     // We are always live and kicking...
166   }
167
168   def insertResourceEvent(event: ResourceEventModel) = {
169     val localEvent = createResourceEventFromOther(event)
170     _resourceEvents ::= localEvent
171     localEvent
172   }
173
174   def findResourceEventByID(id: String) = {
175     _resourceEvents.find(ev ⇒ ev.id == id)
176   }
177
178   def findResourceEventsByUserID(userId: String)
179                                 (sortWith: Option[(ResourceEvent, ResourceEvent) => Boolean]): List[ResourceEvent] = {
180     val byUserId = _resourceEvents.filter(_.userID == userId).toArray
181     val sorted = sortWith match {
182       case Some(sorter) ⇒
183         byUserId.sortWith(sorter)
184       case None ⇒
185         byUserId
186     }
187
188     sorted.toList
189   }
190
191   def countOutOfSyncResourceEventsForBillingPeriod(userID: String, startMillis: Long, stopMillis: Long): Long = {
192     _resourceEvents.filter { case ev ⇒
193       ev.userID == userID &&
194       // out of sync events are those that were received in the billing month but occurred in previous (or next?)
195       // months
196       ev.isOutOfSyncForBillingPeriod(startMillis, stopMillis)
197     }.size.toLong
198   }
199   //- ResourceEventStore
200
201   def foreachResourceEventOccurredInPeriod(
202       userID: String,
203       startMillis: Long,
204       stopMillis: Long
205   )(f: ResourceEvent ⇒ Unit): Unit = {
206     _resourceEvents.filter { case ev ⇒
207       ev.userID == userID &&
208       ev.isOccurredWithinMillis(startMillis, stopMillis)
209     }.foreach(f)
210   }
211
212   //+ IMEventStore
213   def createIMEventFromJson(json: String) = {
214     StdIMEvent.fromJsonString(json)
215   }
216
217   def createIMEventFromOther(event: IMEventModel) = {
218     StdIMEvent.fromOther(event)
219   }
220
221   def pingIMEventStore(): Unit = {
222   }
223
224
225   def insertIMEvent(event: IMEventModel) = {
226     val localEvent = createIMEventFromOther(event)
227     imEventById += (event.id -> localEvent)
228     localEvent
229   }
230
231   def findIMEventByID(id: String) = imEventById.get(id)
232
233
234   /**
235    * Find the `CREATE` even for the given user. Note that there must be only one such event.
236    */
237   def findCreateIMEventByUserID(userID: String): Option[IMEvent] = {
238     imEventById.valuesIterator.filter { e ⇒
239       e.userID == userID && e.isCreateUser
240     }.toList.sortWith { case (e1, e2) ⇒
241       e1.occurredMillis < e2.occurredMillis
242     } headOption
243   }
244
245   def findLatestIMEventByUserID(userID: String): Option[IMEvent] = {
246     imEventById.valuesIterator.filter(_.userID == userID).toList.sortWith {
247       case (us1, us2) ⇒
248         us1.occurredMillis > us2.occurredMillis
249     } headOption
250   }
251
252   /**
253    * Scans events for the given user, sorted by `occurredMillis` in ascending order and runs them through
254    * the given function `f`.
255    *
256    * Any exception is propagated to the caller. The underlying DB resources are properly disposed in any case.
257    */
258   def foreachIMEventInOccurrenceOrder(userID: String)(f: (IMEvent) => Unit) = {
259     imEventById.valuesIterator.filter(_.userID == userID).toSeq.sortWith {
260       case (ev1, ev2) ⇒ ev1.occurredMillis <= ev2.occurredMillis
261     } foreach(f)
262   }
263   //- IMEventStore
264
265   def loadPoliciesAfter(afterMillis: Long) =
266     _policies.filter(p => p.validFrom > afterMillis)
267             .sortWith((a,b) => a.validFrom < b.validFrom)
268
269   def findPolicyByID(id: String) = {
270     _policies.find(p => p.id == id)
271   }
272
273   /**
274    * Store an accounting policy.
275    */
276   def insertPolicy(policy: PolicyModel): Policy = {
277     val localPolicy = StdPolicy(
278       id = policy.id,
279       parentID = policy.parentID,
280       validityTimespan = policy.validityTimespan,
281       resourceTypes = policy.resourceTypes,
282       chargingBehaviors = policy.chargingBehaviors,
283       roleMapping = policy.roleMapping
284     )
285     _policies = localPolicy :: _policies
286
287     localPolicy
288   }
289 }
290
291 object MemStoreProvider {
292   final def isLocalIMEvent(event: IMEventModel) = event match {
293     case _: MemIMEvent ⇒ true
294     case _ ⇒ false
295   }
296 }