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.user
38 import scala.collection.mutable
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}
50 * @author Christos KK Loverdos <loverdos@gmail.com>
52 class UserStateComputations extends Loggable {
53 def createFirstUserState(userId: String, agreementName: String = "default") = {
62 LatestResourceEventsSnapshot(Map(), now),
64 ActiveStateSnapshot(false, now),
65 CreditSnapshot(0, now),
66 AgreementSnapshot(Agreement(agreementName, now, -1) :: Nil, now),
67 RolesSnapshot(List(), now),
68 OwnedResourcesSnapshot(List(), now)
72 def createFirstUserState(userId: String, agreementName: String, resourcesMap: DSLResourcesMap) = {
81 LatestResourceEventsSnapshot(Map(), now),
83 ActiveStateSnapshot(false, now),
84 CreditSnapshot(0, now),
85 AgreementSnapshot(Agreement(agreementName, now, - 1) :: Nil, now),
86 RolesSnapshot(List(), now),
87 OwnedResourcesSnapshot(List(), now)
91 def findUserStateAtEndOfBillingMonth(userId: String,
92 yearOfBillingMonth: 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] = {
104 def D(fmt: String, args: Any*) = {
105 logger.debug("[%s, %s-%02d] %s".format(userId, yearOfBillingMonth, billingMonth, fmt.format(args:_*)))
108 def E(fmt: String, args: Any*) = {
109 logger.error("[%s, %s-%02d] %s".format(userId, yearOfBillingMonth, billingMonth, fmt.format(args:_*)))
112 def W(fmt: String, args: Any*) = {
113 logger.error("[%s, %s-%02d] %s".format(userId, yearOfBillingMonth, billingMonth, fmt.format(args:_*)))
116 def doCompute: Maybe[UserState] = {
117 D("Computing full month billing")
118 doFullMonthlyBilling(
133 val billingMonthStartDateCalc = new DateCalculator(yearOfBillingMonth, billingMonth)
134 val billingMonthStartMillis = billingMonthStartDateCalc.toMillis
135 val billingMonthStopMillis = billingMonthStartDateCalc.goEndOfThisMonth.toMillis
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)
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)
148 // No out of sync events, ask DB cache
149 userStateStore.findLatestUserStateForEndOfBillingMonth(userId, yearOfBillingMonth, billingMonth) match {
150 case just @ Just(userState) ⇒
152 D("Found from cache: %s", userState)
156 D("No user state found from cache, will have to (re)compute")
158 case failed @ Failed(_, _) ⇒
159 W("Failure while quering cache for user state: %s", failed)
162 case failed @ Failed(_, _) ⇒
163 W("Failure while querying for out of sync events: %s", failed)
169 def doFullMonthlyBilling(userId: String,
170 yearOfBillingMonth: 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(
196 previousBillingMonthUserStateM match {
198 NoVal // not really...
199 case failed @ Failed(_, _) ⇒
201 case Just(startingUserState) ⇒
202 // This is the real deal
204 val billingMonthStartDateCalc = new DateCalculator(yearOfBillingMonth, billingMonth)
205 val billingMonthEndDateCalc = billingMonthStartDateCalc.copy.goEndOfThisMonth
206 val billingMonthStartMillis = billingMonthStartDateCalc.toMillis
207 val billingMonthEndMillis = billingMonthEndDateCalc.toMillis
209 // Keep the working (current) user state. This will get updated as we proceed billing within the month
210 var _workingUserState = startingUserState
212 val allResourceEventsForMonth = resourceEventStore.findAllRelevantResourceEventsForBillingPeriod(
214 billingMonthStartMillis,
215 billingMonthEndMillis)
223 * Find the previous resource event, if needed by the event's cost policy,
224 * in order to use it for any credit calculations.
226 def findPreviousRCEventOf(rcEvent: ResourceEvent,
227 costPolicy: DSLCostPolicy,
228 previousRCEventsMap: mutable.Map[ResourceEvent.FullResourceType, ResourceEvent]): Maybe[ResourceEvent] = {
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)
236 queryForPreviousRCEvent(rcEvent)
239 // No need for previous event. Will return NoVal
247 def queryForPreviousRCEvent(rcEvent: ResourceEvent): Maybe[ResourceEvent] = {
251 type FullResourceType = ResourceEvent.FullResourceType
252 def updatePreviousRCEventWith(previousRCEventsMap: mutable.Map[FullResourceType, ResourceEvent],
253 newRCEvent: ResourceEvent): Unit = {
254 previousRCEventsMap(newRCEvent.fullResourceInfo) = newRCEvent
258 object DefaultUserStateComputations extends UserStateComputations