import com.ckkloverdos.maybe.{Failed, NoVal, Just, Maybe}
-import gr.grnet.aquarium.util.date.MutableDateCalc
-import gr.grnet.aquarium.logic.accounting.dsl.{DSLResourcesMap, DSLPolicy}
-import gr.grnet.aquarium.logic.events.ResourceEvent
import gr.grnet.aquarium.store.{PolicyStore, UserStateStore, ResourceEventStore}
import gr.grnet.aquarium.util.{ContextualLogger, Loggable}
import gr.grnet.aquarium.logic.accounting.Accounting
+import gr.grnet.aquarium.logic.accounting.algorithm.SimpleCostPolicyAlgorithmCompiler
+import gr.grnet.aquarium.logic.events.{NewWalletEntry, ResourceEvent}
+import gr.grnet.aquarium.util.date.{TimeHelpers, MutableDateCalc}
+import gr.grnet.aquarium.logic.accounting.dsl.{DSLAgreement, DSLCostPolicy, DSLResourcesMap, DSLPolicy}
/**
*
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
class UserStateComputations extends Loggable {
- def createFirstUserState(userId: String, agreementName: String = "default") = {
+ def createFirstUserState(userId: String,
+ millis: Long = TimeHelpers.nowMillis,
+ agreementName: String = DSLAgreement.DefaultAgreementName) = {
val now = 0L
UserState(
userId,
0L,
false,
null,
- ImplicitlyIssuedResourceEventsSnapshot(Map(), now),
+ ImplicitlyIssuedResourceEventsSnapshot(List(), now),
Nil, Nil,
- LatestResourceEventsSnapshot(Map(), now),
+ LatestResourceEventsSnapshot(List(), now),
0L, 0L,
ActiveStateSnapshot(false, now),
CreditSnapshot(0, now),
0L,
false,
null,
- ImplicitlyIssuedResourceEventsSnapshot(Map(), now),
+ ImplicitlyIssuedResourceEventsSnapshot(List(), now),
Nil, Nil,
- LatestResourceEventsSnapshot(Map(), now),
+ LatestResourceEventsSnapshot(List(), now),
0L, 0L,
ActiveStateSnapshot(false, now),
CreditSnapshot(0, now),
if(billingMonthStopMillis < userCreationMillis) {
// If the user did not exist for this billing month, piece of cake
- clog.debug("User did not exist before %s. Returning %s", userCreationDateCalc, zeroUserState)
+ clog.debug("User did not exist before %s", userCreationDateCalc)
+ clog.debug("Returning ZERO state %s".format(zeroUserState))
clog.endWith(Just(zeroUserState))
} else {
// Ask DB cache for the latest known user state for this billing period
accounting: Accounting,
contextualLogger: Maybe[ContextualLogger] = NoVal): Maybe[UserState] = Maybe {
+ def rcDebugInfo(rcEvent: ResourceEvent) = {
+ rcEvent.toDebugString(defaultResourcesMap, false)
+ }
+
val clog = ContextualLogger.fromOther(
contextualLogger,
logger,
// specified in the parameters.
var _workingUserState = startingUserState
- // Prepare the implicit OFF resource events
- val theImplicitOFFs = _workingUserState.implicitlyTerminatedSnapshot.toMutableWorker
- clog.debug("theImplicitOFFs = %s", theImplicitOFFs)
+ // Prepare the implicitly terminated resource events from previous billing period
+ val implicitlyTerminatedResourceEvents = _workingUserState.implicitlyTerminatedSnapshot.toMutableWorker
+ if(implicitlyTerminatedResourceEvents.size > 0) {
+ clog.debug("%s implicitlyTerminatedResourceEvents", implicitlyTerminatedResourceEvents.size)
+ clog.withIndent {
+ implicitlyTerminatedResourceEvents.foreach(ev ⇒ clog.debug("%s", rcDebugInfo(ev)))
+ }
+ }
+
+ // Keep the resource events from this period that were first (and unused) of their kind
+ val ignoredFirstResourceEvents = IgnoredFirstResourceEventsWorker.Empty
/**
- * Finds the previous resource event by checking two possible sources: a) The implicit OFF resource events and
- * b) the explicit previous resource events. If the event is found, it is removed from the respective source.
+ * Finds the previous resource event by checking two possible sources: a) The implicitly terminated resource
+ * events and b) the explicit previous resource events. If the event is found, it is removed from the
+ * respective source.
*
* If the event is not found, then this must be for a new resource instance.
* (and probably then some `zero` resource event must be implied as the previous one)
* @return
*/
def findAndRemovePreviousResourceEvent(resource: String, instanceId: String): Maybe[ResourceEvent] = {
- // implicit OFFs are checked first
- theImplicitOFFs.findAndRemoveResourceEvent(resource, instanceId) match {
+ // implicitly terminated events are checked first
+ implicitlyTerminatedResourceEvents.findAndRemoveResourceEvent(resource, instanceId) match {
case just @ Just(_) ⇒
just
case NoVal ⇒
- // explicit previous are checked second
+ // explicit previous resource events are checked second
previousResourceEvents.findAndRemoveResourceEvent(resource, instanceId) match {
case just @ Just(_) ⇒
just
}
}
- def rcDebugInfo(rcEvent: ResourceEvent) = {
- rcEvent.toDebugString(defaultResourcesMap, false)
- }
-
// Find the actual resource events from DB
val allResourceEventsForMonth = resourceEventStore.findAllRelevantResourceEventsForBillingPeriod(
userId,
var _eventCounter = 0
clog.debug("resourceEventStore = %s".format(resourceEventStore))
- clog.debug("Found %s resource events, starting processing...", allResourceEventsForMonth.size)
-
+ if(allResourceEventsForMonth.size > 0) {
+ clog.debug("Found %s resource events, starting processing...", allResourceEventsForMonth.size)
+ } else {
+ clog.debug("Not found any resource events")
+ }
+
for {
currentResourceEvent <- allResourceEventsForMonth
} {
_eventCounter = _eventCounter + 1
- val theResource = currentResourceEvent.resource
- val theInstanceId = currentResourceEvent.instanceId
+ val theResource = currentResourceEvent.safeResource
+ val theInstanceId = currentResourceEvent.safeInstanceId
val theValue = currentResourceEvent.value
clog.indent()
+ clog.debug("")
clog.debug("Processing %s", currentResourceEvent)
- clog.debug("Friendlier %s", rcDebugInfo(currentResourceEvent))
+ clog.debug("+========= %s", rcDebugInfo(currentResourceEvent))
+
clog.indent()
if(previousResourceEvents.size > 0) {
clog.debug("%s previousResourceEvents", previousResourceEvents.size)
- clog.indent()
- previousResourceEvents.foreach(ev ⇒ clog.debug("%s", rcDebugInfo(ev)))
- clog.unindent()
+ clog.withIndent {
+ previousResourceEvents.foreach(ev ⇒ clog.debug("%s", rcDebugInfo(ev)))
+ }
}
- if(theImplicitOFFs.size > 0) {
- clog.debug("%s theImplicitOFFs", theImplicitOFFs.size)
- clog.indent()
- theImplicitOFFs.foreach(ev ⇒ clog.debug("%s", rcDebugInfo(ev)))
- clog.unindent()
+ if(implicitlyTerminatedResourceEvents.size > 0) {
+ clog.debug("%s implicitlyTerminatedResourceEvents", implicitlyTerminatedResourceEvents.size)
+ clog.withIndent {
+ implicitlyTerminatedResourceEvents.foreach(ev ⇒ clog.debug("%s", rcDebugInfo(ev)))
+ }
+ }
+ if(ignoredFirstResourceEvents.size > 0) {
+ clog.debug("%s ignoredFirstResourceEvents", ignoredFirstResourceEvents.size)
+ clog.withIndent {
+ ignoredFirstResourceEvents.foreach(ev ⇒ clog.debug("%s", rcDebugInfo(ev)))
+ }
}
// Ignore the event if it is not billable (but still record it in the "previous" stuff).
- // But to make this decision, first we need the resource definiton (and its cost policy).
- val resourceDefM = defaultResourcesMap.findResourceM(currentResourceEvent.safeResource)
+ // But to make this decision, first we need the resource definition (and its cost policy).
+ val resourceDefM = defaultResourcesMap.findResourceM(theResource)
resourceDefM match {
// We have a resource (and thus a cost policy)
case Just(resourceDef) ⇒
val costPolicy = resourceDef.costPolicy
clog.debug("Cost policy: %s", costPolicy)
- val isBillable = costPolicy.isBillableEventBasedOnValue(currentResourceEvent.value)
+ val isBillable = costPolicy.isBillableEventBasedOnValue(theValue)
isBillable match {
// The resource event is not billable
case false ⇒
// This is (potentially) needed to calculate new credit amount and new resource instance amount
val previousResourceEventM = findAndRemovePreviousResourceEvent(theResource, theInstanceId)
clog.debug("PreviousM %s", previousResourceEventM.map(rcDebugInfo(_)))
- val defaultInitialAmount = costPolicy.getResourceInstanceInitialAmount
- val oldAmount = _workingUserState.getResourceInstanceAmount(theResource, theInstanceId, defaultInitialAmount)
- val oldCredits = _workingUserState.creditsSnapshot.creditAmount
-
- // A. Compute new resource instance accumulating amount
- val newAmount = costPolicy.computeNewAccumulatingAmount(oldAmount, theValue)
-
- // B. Compute new wallet entries
- val alltimeAgreements = _workingUserState.agreementsSnapshot.agreements
-// val chargeChunksM = accounting.computeChargeChunks(
-// previousResourceEventM,
-// currentResourceEvent,
-// oldCredits,
-// oldAmount,
-// newAmount,
-// resourceDef,
-// defaultResourcesMap,
-// alltimeAgreements)
-
- // C. Compute new credit amount (based on the wallet entries)
- // Maybe this can be consolidated inthe previous step (B)
-
-
- ()
+
+ val havePreviousResourceEvent = previousResourceEventM.isJust
+ val needPreviousResourceEvent = costPolicy.needsPreviousEventForCreditAndAmountCalculation
+ if(needPreviousResourceEvent && !havePreviousResourceEvent) {
+ // This must be the first resource event of its kind, ever.
+ // TODO: We should normally check the DB to verify the claim (?)
+ clog.info("Ignoring first event of its kind %s", rcDebugInfo(currentResourceEvent))
+ ignoredFirstResourceEvents.updateResourceEvent(currentResourceEvent)
+ } else {
+ val defaultInitialAmount = costPolicy.getResourceInstanceInitialAmount
+ val oldAmount = _workingUserState.getResourceInstanceAmount(theResource, theInstanceId, defaultInitialAmount)
+ val oldCredits = _workingUserState.creditsSnapshot.creditAmount
+
+ // A. Compute new resource instance accumulating amount
+ val newAmount = costPolicy.computeNewAccumulatingAmount(oldAmount, theValue)
+
+ clog.debug("theValue = %s, oldAmount = %s, newAmount = %s, oldCredits = %s", theValue, oldAmount, newAmount, oldCredits)
+
+ // B. Compute new wallet entries
+ val alltimeAgreements = _workingUserState.agreementsSnapshot.agreementsByTimeslot
+
+ val fullChargeslotsM = accounting.computeFullChargeslots(
+ previousResourceEventM,
+ currentResourceEvent,
+ oldCredits,
+ oldAmount,
+ newAmount,
+ resourceDef,
+ defaultResourcesMap,
+ alltimeAgreements,
+ SimpleCostPolicyAlgorithmCompiler,
+ policyStore,
+ Just(clog)
+ )
+
+ // We have the chargeslots, let's associate them with the current event
+ fullChargeslotsM match {
+ case Just(fullChargeslots) ⇒
+ if(fullChargeslots.length == 0) {
+ // At least one chargeslot is required.
+ throw new Exception("No chargeslots computed")
+ }
+ clog.debug("chargeslots:")
+ clog.withIndent {
+ for(fullChargeslot <- fullChargeslots) {
+ clog.debug("%s", fullChargeslot)
+ }
+ }
+
+ // C. Compute new credit amount (based on the charge slots)
+ val newCreditsDiff = fullChargeslots.map(_.computedCredits.get).sum
+ val newCredits = oldCredits + newCreditsDiff
+ clog.debug("newCreditsDiff = %s, newCredits = %s", newCreditsDiff, newCredits)
+
+ val newWalletEntry = NewWalletEntry(
+ userId,
+ newCreditsDiff,
+ oldCredits,
+ newCredits,
+ TimeHelpers.nowMillis,
+ billingMonthInfo.year,
+ billingMonthInfo.month,
+ currentResourceEvent,
+ previousResourceEventM.toOption,
+ fullChargeslots,
+ resourceDef
+ )
+
+ clog.debug("New %s", newWalletEntry)
+
+ case NoVal ⇒
+ // At least one chargeslot is required.
+ throw new Exception("No chargeslots computed")
+
+ case failed @ Failed(e, m) ⇒
+ throw new Exception(m, e)
+ }
+ }
+
}
// After processing, all event, billable or not update the previous state
}
clog.unindent()
+ clog.debug("-========= %s", rcDebugInfo(currentResourceEvent))
clog.unindent()
}