Rework data snapshots. Add one more to user state
[aquarium] / src / main / scala / gr / grnet / aquarium / user / UserStateComputations.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.user
37
38 import scala.collection.mutable
39
40 import com.ckkloverdos.maybe.{Failed, NoVal, Just, Maybe}
41 import gr.grnet.aquarium.logic.accounting.Accounting
42 import gr.grnet.aquarium.util.date.DateCalculator
43 import gr.grnet.aquarium.logic.accounting.dsl.{DSLResourcesMap, DSLCostPolicy, DSLPolicy}
44 import gr.grnet.aquarium.util.Loggable
45 import gr.grnet.aquarium.logic.events.ResourceEvent
46 import gr.grnet.aquarium.store.{PolicyStore, UserStateStore, ResourceEventStore}
47
48 /**
49  *
50  * @author Christos KK Loverdos <loverdos@gmail.com>
51  */
52 class UserStateComputations extends Loggable {
53   def createFirstUserState(userId: String, agreementName: String = "default") = {
54     val now = 0L
55     UserState(
56       userId,
57       now,
58       0L,
59       false,
60       null,
61       Nil, Nil, Nil,
62       LatestResourceEventsSnapshot(Map(), now),
63       0L,
64       ActiveStateSnapshot(false, now),
65       CreditSnapshot(0, now),
66       AgreementSnapshot(Agreement(agreementName, now, -1) :: Nil, now),
67       RolesSnapshot(List(), now),
68       OwnedResourcesSnapshot(List(), now)
69     )
70   }
71
72   def createFirstUserState(userId: String, agreementName: String, resourcesMap: DSLResourcesMap) = {
73       val now = 0L
74       UserState(
75         userId,
76         now,
77         0L,
78         false,
79         null,
80         Nil, Nil,Nil,
81         LatestResourceEventsSnapshot(Map(), now),
82         0L,
83         ActiveStateSnapshot(false, now),
84         CreditSnapshot(0, now),
85         AgreementSnapshot(Agreement(agreementName, now, - 1) :: Nil, now),
86         RolesSnapshot(List(), now),
87         OwnedResourcesSnapshot(List(), now)
88       )
89     }
90
91   def findUserStateAtEndOfBillingMonth(userId: String,
92                                        yearOfBillingMonth: Int,
93                                        billingMonth: Int,
94                                        userStateStore: UserStateStore,
95                                        resourceEventStore: ResourceEventStore,
96                                        policyStore: PolicyStore,
97                                        userCreationMillis: Long,
98                                        currentUserState: UserState,
99                                        zeroUserState: UserState, 
100                                        defaultPolicy: DSLPolicy,
101                                        defaultResourcesMap: DSLResourcesMap,
102                                        accounting: Accounting): Maybe[UserState] = {
103
104     def D(fmt: String, args: Any*) = {
105       logger.debug("[%s, %s-%02d] %s".format(userId, yearOfBillingMonth, billingMonth, fmt.format(args:_*)))
106     }
107
108     def E(fmt: String, args: Any*) = {
109       logger.error("[%s, %s-%02d] %s".format(userId, yearOfBillingMonth, billingMonth, fmt.format(args:_*)))
110     }
111
112     def W(fmt: String, args: Any*) = {
113       logger.error("[%s, %s-%02d] %s".format(userId, yearOfBillingMonth, billingMonth, fmt.format(args:_*)))
114     }
115
116     def doCompute: Maybe[UserState] = {
117       D("Computing full month billing")
118       doFullMonthlyBilling(
119         userId,
120         yearOfBillingMonth,
121         billingMonth,
122         userStateStore,
123         resourceEventStore,
124         policyStore,
125         userCreationMillis,
126         currentUserState,
127         zeroUserState,
128         defaultPolicy,
129         defaultResourcesMap,
130         accounting)
131     }
132
133     val billingMonthStartDateCalc = new DateCalculator(yearOfBillingMonth, billingMonth)
134     val billingMonthStartMillis = billingMonthStartDateCalc.toMillis
135     val billingMonthStopMillis  = billingMonthStartDateCalc.goEndOfThisMonth.toMillis
136
137     if(billingMonthStartMillis > userCreationMillis) {
138       // If the user did not exist for this billing month, piece of cake
139       D("User did not exists before %s. Returning %s", new DateCalculator(userCreationMillis), zeroUserState)
140       Just(zeroUserState)
141     } else {
142       resourceEventStore.countOutOfSyncEventsForBillingPeriod(userId, billingMonthStartMillis, billingMonthStopMillis) match {
143         case Just(outOfSyncEventCount) ⇒
144           // Have out of sync, so must recompute
145           D("Found %s out of sync events, will have to (re)compute user state", outOfSyncEventCount)
146           doCompute
147         case NoVal ⇒
148           // No out of sync events, ask DB cache
149           userStateStore.findLatestUserStateForEndOfBillingMonth(userId, yearOfBillingMonth, billingMonth) match {
150             case just @ Just(userState) ⇒
151               // Found from cache
152               D("Found from cache: %s", userState)
153               just
154             case NoVal ⇒
155               // otherwise compute
156               D("No user state found from cache, will have to (re)compute")
157               doCompute
158             case failed @ Failed(_, _) ⇒
159               W("Failure while quering cache for user state: %s", failed)
160               failed
161           }
162         case failed @ Failed(_, _) ⇒
163           W("Failure while querying for out of sync events: %s", failed)
164           failed
165       }
166     }
167   }
168
169   def doFullMonthlyBilling(userId: String,
170                            yearOfBillingMonth: Int,
171                            billingMonth: Int,
172                            userStateStore: UserStateStore,
173                            resourceEventStore: ResourceEventStore,
174                            policyStore: PolicyStore,
175                            userCreationMillis: Long,
176                            currentUserState: UserState,
177                            zeroUserState: UserState,
178                            defaultPolicy: DSLPolicy,
179                            defaultResourcesMap: DSLResourcesMap,
180                            accounting: Accounting): Maybe[UserState] = Maybe {
181     val previousBillingMonthUserStateM = findUserStateAtEndOfBillingMonth(
182       userId,
183       yearOfBillingMonth,
184       billingMonth,
185       userStateStore,
186       resourceEventStore,
187       policyStore,
188       userCreationMillis,
189       currentUserState,
190       zeroUserState,
191       defaultPolicy,
192       defaultResourcesMap,
193       accounting
194     )
195     
196     previousBillingMonthUserStateM match {
197       case NoVal ⇒
198         NoVal // not really...
199       case failed @ Failed(_, _) ⇒
200         failed
201       case Just(startingUserState) ⇒
202         // This is the real deal
203
204         val billingMonthStartDateCalc = new DateCalculator(yearOfBillingMonth, billingMonth)
205         val billingMonthEndDateCalc   = billingMonthStartDateCalc.copy.goEndOfThisMonth
206         val billingMonthStartMillis = billingMonthStartDateCalc.toMillis
207         val billingMonthEndMillis  = billingMonthEndDateCalc.toMillis
208
209         // Keep the working (current) user state. This will get updated as we proceed billing within the month
210         var _workingUserState = startingUserState
211
212         val allResourceEventsForMonth = resourceEventStore.findAllRelevantResourceEventsForBillingPeriod(
213           userId,
214           billingMonthStartMillis,
215           billingMonthEndMillis)
216     }
217
218     null
219   }
220
221
222   /**
223    * Find the previous resource event, if needed by the event's cost policy,
224    * in order to use it for any credit calculations.
225    */
226   def findPreviousRCEventOf(rcEvent: ResourceEvent,
227                             costPolicy: DSLCostPolicy,
228                             previousRCEventsMap: mutable.Map[ResourceEvent.FullResourceType, ResourceEvent]): Maybe[ResourceEvent] = {
229
230     if(costPolicy.needsPreviousEventForCreditCalculation) {
231       // Get a previous resource only if this is needed by the policy
232       previousRCEventsMap.get(rcEvent.fullResourceInfo) match {
233         case Some(previousRCEvent) ⇒
234           Just(previousRCEvent)
235         case None ⇒
236           queryForPreviousRCEvent(rcEvent)
237       }
238     } else {
239       // No need for previous event. Will return NoVal
240       NoVal
241     }
242   }
243
244   /**
245    * FIXME: implement
246    */
247   def queryForPreviousRCEvent(rcEvent: ResourceEvent): Maybe[ResourceEvent] = {
248     NoVal
249   }
250
251   type FullResourceType = ResourceEvent.FullResourceType
252   def updatePreviousRCEventWith(previousRCEventsMap: mutable.Map[FullResourceType, ResourceEvent],
253                                 newRCEvent: ResourceEvent): Unit = {
254     previousRCEventsMap(newRCEvent.fullResourceInfo) = newRCEvent
255   }
256 }
257
258 object DefaultUserStateComputations extends UserStateComputations