Implement Once behavior with the new scheme. Refactor in the process
[aquarium] / src / main / scala / gr / grnet / aquarium / charging / ChargingService.scala
index 7491669..f2aa992 100644 (file)
 
 package gr.grnet.aquarium.charging
 
+import scala.collection.mutable
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
 import gr.grnet.aquarium.computation.BillingMonthInfo
-import gr.grnet.aquarium.charging.state.UserStateBootstrap
+import gr.grnet.aquarium.charging.state.{WorkingResourcesChargingState, UserStateBootstrap, WorkingUserState, UserStateModel, StdUserState}
 import gr.grnet.aquarium.policy.ResourceType
-import gr.grnet.aquarium.util.{Lifecycle, Loggable, ContextualLogger}
+import gr.grnet.aquarium.util.{Lifecycle, Loggable}
+import gr.grnet.aquarium.util.LogHelpers.Debug
+import gr.grnet.aquarium.util.LogHelpers.Warn
 import gr.grnet.aquarium.util.date.{MutableDateCalc, TimeHelpers}
 import gr.grnet.aquarium.{AquariumInternalError, AquariumAwareSkeleton}
-import gr.grnet.aquarium.charging.state.{WorkingUserState, UserStateModel, StdUserState}
 import gr.grnet.aquarium.charging.reason.{MonthlyBillChargingReason, InitialUserStateSetup, ChargingReason}
 
 /**
@@ -56,9 +58,9 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
   lazy val resourceEventStore = aquarium.resourceEventStore
 
   //+ Lifecycle
-  def start() = ()
+  def start() {}
 
-  def stop() = ()
+  def stop() {}
   //- Lifecycle
 
 
@@ -73,36 +75,31 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       userStateBootstrap: UserStateBootstrap,
       defaultResourceTypesMap: Map[String, ResourceType],
       chargingReason: ChargingReason,
-      userStateRecorder: UserStateModel ⇒ UserStateModel,
-      clogOpt: Option[ContextualLogger]
+      userStateRecorder: UserStateModel ⇒ UserStateModel
   ): WorkingUserState = {
 
-    val clog = ContextualLogger.fromOther(
-      clogOpt,
-      logger,
-      "findOrCalculateWorkingUserStateAtEndOfBillingMonth(%s)", billingMonthInfo.toShortDebugString)
-    clog.begin()
-
-    lazy val clogSome = Some(clog)
-
     def computeFullMonthBillingAndSaveState(): WorkingUserState = {
       val workingUserState = replayFullMonthBilling(
         userStateBootstrap,
         billingMonthInfo,
         defaultResourceTypesMap,
         chargingReason,
-        userStateRecorder,
-        clogSome
+        userStateRecorder
       )
 
       val newChargingReason = MonthlyBillChargingReason(chargingReason, billingMonthInfo)
       workingUserState.chargingReason = newChargingReason
-      val monthlyUserState0 = workingUserState.toUserState(Some(billingMonthInfo), None)
+      val monthlyUserState0 = workingUserState.toUserState(
+        true,
+        billingMonthInfo.year,
+        billingMonthInfo.month,
+        ""
+      )
 
       // We always save the state when it is a full month billing
       val monthlyUserState1 = userStateRecorder.apply(monthlyUserState0)
 
-      clog.debug("Stored full %s %s", billingMonthInfo.toDebugString, monthlyUserState1.toJsonString)
+      Debug(logger, "Stored full %s %s", billingMonthInfo.toDebugString, monthlyUserState1.toJsonString)
 
       workingUserState
     }
@@ -115,7 +112,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
 
     if(billingMonthStopMillis < userCreationMillis) {
       // If the user did not exist for this billing month, piece of cake
-      clog.debug("User did not exist before %s", userCreationDateCalc)
+      Debug(logger, "User did not exist before %s", userCreationDateCalc)
 
       // TODO: The initial user state might have already been created.
       //       First ask if it exists and compute only if not
@@ -125,13 +122,12 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
         InitialUserStateSetup(Some(chargingReason)) // we record the originating calculation reason
       )
 
-      logger.debug("Created (from bootstrap) initial user state %s".format(initialUserState0))
+      Debug(logger, "Created (from bootstrap) initial user state %s", initialUserState0)
 
       // We always save the initial state
       val initialUserState1 = userStateRecorder.apply(initialUserState0)
 
-      clog.debug("Stored initial state = %s", initialUserState1.toJsonString)
-      clog.end()
+      Debug(logger, "Stored initial state = %s", initialUserState1.toJsonString)
 
       return initialUserState1.toWorkingUserState(defaultResourceTypesMap)
     }
@@ -144,10 +140,8 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
     latestUserStateOpt match {
       case None ⇒
         // Not found, must compute
-        clog.debug("No user state found from cache, will have to (re)compute")
-        val result = computeFullMonthBillingAndSaveState
-        clog.end()
-        result
+        Debug(logger, "No user state found from cache, will have to (re)compute")
+        computeFullMonthBillingAndSaveState
 
       case Some(latestUserState) ⇒
         // Found a "latest" user state but need to see if it is indeed the true and one latest.
@@ -164,83 +158,107 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
           case 0 ⇒
             // NOTE: Keep the caller's calculation reason
             val userStateModel = latestUserState.newWithChargingReason(chargingReason)
-            clog.end()
             userStateModel.toWorkingUserState(defaultResourceTypesMap)
 
           // We had more, so must recompute
           case n if n > 0 ⇒
-            clog.debug(
+            Debug(logger,
               "Found %s out of sync events (%s more), will have to (re)compute user state", actualOOSEventsCounter, n)
-            val workingUserState = computeFullMonthBillingAndSaveState
-            clog.end()
-            workingUserState
+            computeFullMonthBillingAndSaveState
 
           // We had less????
           case n if n < 0 ⇒
             val errMsg = "Found %s out of sync events (%s less). DB must be inconsistent".format(actualOOSEventsCounter, n)
-            clog.warn(errMsg)
+            Warn(logger, errMsg)
             throw new AquariumInternalError(errMsg)
         }
     }
   }
   /**
-   * Processes one resource event and computes relevant charges.
+   * Processes one resource event and computes relevant, incremental charges.
    *
    * @param resourceEvent
    * @param workingUserState
    * @param chargingReason
    * @param billingMonthInfo
-   * @param clogOpt
    */
   def processResourceEvent(
       resourceEvent: ResourceEventModel,
       workingUserState: WorkingUserState,
       chargingReason: ChargingReason,
       billingMonthInfo: BillingMonthInfo,
-      clogOpt: Option[ContextualLogger]
-  ): Unit = {
+      updateLatestMillis: Boolean
+  ): Boolean = {
 
     val resourceTypeName = resourceEvent.resource
     val resourceTypeOpt = workingUserState.findResourceType(resourceTypeName)
     if(resourceTypeOpt.isEmpty) {
-      return
+      // Unknown (yet) resource, ignoring event.
+      return false
     }
     val resourceType = resourceTypeOpt.get
-    val resourceAndInstanceInfo = resourceEvent.safeResourceInstanceInfo
 
     val chargingBehavior = aquarium.chargingBehaviorOf(resourceType)
+    val workingResourcesState = workingUserState.workingStateOfResources.get(resourceTypeName) match {
+      case Some(existingState) ⇒
+        existingState
 
-    val (walletEntriesCount, newTotalCredits) = chargingBehavior.chargeResourceEvent(
+      case None ⇒
+        // First time for this ChargingBehavior.
+        val newState = new WorkingResourcesChargingState(
+          details = mutable.Map(chargingBehavior.initialChargingDetails.toSeq:_*),
+          stateOfResourceInstance = mutable.Map()
+        )
+
+        workingUserState.workingStateOfResources(resourceTypeName) = newState
+        newState
+    }
+
+    val m0 = TimeHelpers.nowMillis()
+    val (walletEntriesCount, newTotalCredits) = chargingBehavior.processResourceEvent(
       aquarium,
       resourceEvent,
       resourceType,
       billingMonthInfo,
-      workingUserState.workingAgreementHistory.toAgreementHistory,
-      workingUserState.getChargingDataForResourceEvent(resourceAndInstanceInfo),
+      workingResourcesState,
+      workingUserState.workingAgreementHistory,
       workingUserState.totalCredits,
-      workingUserState.walletEntries += _,
-      clogOpt
+      workingUserState.walletEntries += _
     )
+    val m1 = TimeHelpers.nowMillis()
+
+    if(updateLatestMillis) {
+      workingUserState.latestUpdateMillis = m1
+    }
 
+    workingUserState.updateLatestResourceEventOccurredMillis(resourceEvent.occurredMillis)
     workingUserState.totalCredits = newTotalCredits
+
+    true
   }
 
   def processResourceEvents(
       resourceEvents: Traversable[ResourceEventModel],
       workingUserState: WorkingUserState,
       chargingReason: ChargingReason,
-      billingMonthInfo: BillingMonthInfo,
-      clogOpt: Option[ContextualLogger] = None
+      billingMonthInfo: BillingMonthInfo
   ): Unit = {
 
+    var _counter = 0
     for(currentResourceEvent ← resourceEvents) {
       processResourceEvent(
         currentResourceEvent,
         workingUserState,
         chargingReason,
         billingMonthInfo,
-        clogOpt
+        false
       )
+
+      _counter += 1
+    }
+
+    if(_counter > 0) {
+      workingUserState.latestUpdateMillis = TimeHelpers.nowMillis()
     }
   }
 
@@ -249,8 +267,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       billingMonthInfo: BillingMonthInfo,
       defaultResourceTypesMap: Map[String, ResourceType],
       chargingReason: ChargingReason,
-      userStateRecorder: UserStateModel ⇒ UserStateModel,
-      clogOpt: Option[ContextualLogger]
+      userStateRecorder: UserStateModel ⇒ UserStateModel
   ): WorkingUserState = {
 
     replayMonthChargingUpTo(
@@ -259,8 +276,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       userStateBootstrap,
       defaultResourceTypesMap,
       chargingReason,
-      userStateRecorder,
-      clogOpt
+      userStateRecorder
     )
   }
 
@@ -274,7 +290,6 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
    * @param resourceTypesMap
    * @param chargingReason
    * @param userStateRecorder
-   * @param clogOpt
    * @return
    */
   def replayMonthChargingUpTo(
@@ -283,22 +298,13 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       userStateBootstrap: UserStateBootstrap,
       resourceTypesMap: Map[String, ResourceType],
       chargingReason: ChargingReason,
-      userStateRecorder: UserStateModel ⇒ UserStateModel,
-      clogOpt: Option[ContextualLogger]
+      userStateRecorder: UserStateModel ⇒ UserStateModel
   ): WorkingUserState = {
 
     val isFullMonthBilling = billingEndTimeMillis == billingMonthInfo.monthStopMillis
     val userID = userStateBootstrap.userID
 
-    val clog = ContextualLogger.fromOther(
-      clogOpt,
-      logger,
-      "replayMonthChargingUpTo(%s)", TimeHelpers.toYYYYMMDDHHMMSSSSS(billingEndTimeMillis))
-    clog.begin()
-
-    clog.debug("%s", chargingReason)
-
-    val clogSome = Some(clog)
+    Debug(logger, "%s", chargingReason)
 
     // In order to replay the full month, we start with the state at the beginning of the month.
     val previousBillingMonthInfo = billingMonthInfo.previousMonth
@@ -307,8 +313,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       userStateBootstrap,
       resourceTypesMap,
       chargingReason,
-      userStateRecorder,
-      clogSome
+      userStateRecorder
     )
 
     // FIXME the below comments
@@ -316,9 +321,10 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
     // specified in the parameters.
     // NOTE: The calculation reason is not the one we get from the previous user state but the one our caller specifies
 
-    clog.debug("previousBillingMonthUserState(%s) = %s".format(
+    Debug(logger, "workingUserState=%s", workingUserState)
+    Debug(logger, "previousBillingMonthUserState(%s) = %s",
       previousBillingMonthInfo.toShortDebugString,
-      workingUserState.toJsonString)
+      workingUserState
     )
 
     var _rcEventsCounter = 0
@@ -328,22 +334,29 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       billingEndTimeMillis               // to requested time
     ) { currentResourceEvent ⇒
 
-      clog.debug("Processing %s".format(currentResourceEvent))
+      Debug(logger, "Processing %s", currentResourceEvent)
 
       processResourceEvent(
         currentResourceEvent,
         workingUserState,
         chargingReason,
         billingMonthInfo,
-        clogSome
+        false
       )
 
       _rcEventsCounter += 1
     }
 
-    clog.debug("Found %s resource events for month %s".format(_rcEventsCounter, billingMonthInfo.toShortDebugString))
+    if(_rcEventsCounter > 0) {
+      workingUserState.latestUpdateMillis = TimeHelpers.nowMillis()
+    }
+
+    Debug(logger, "Found %s resource events for month %s",
+      _rcEventsCounter,
+      billingMonthInfo.toShortDebugString
+    )
 
-    if(isFullMonthBilling) {
+    /*if(isFullMonthBilling) {
       // For the remaining events which must contribute an implicit OFF, we collect those OFFs
       // ... in order to generate an implicit ON later (during the next billing cycle).
       val (generatorsOfImplicitEnds, theirImplicitEnds) = workingUserState.findAndRemoveGeneratorsOfImplicitEndEvents(
@@ -352,10 +365,10 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       )
 
       if(generatorsOfImplicitEnds.lengthCompare(1) >= 0 || theirImplicitEnds.lengthCompare(1) >= 0) {
-        clog.debug("")
-        clog.debug("Process implicitly issued events")
-        clog.debugSeq("generatorsOfImplicitEnds", generatorsOfImplicitEnds, 0)
-        clog.debugSeq("theirImplicitEnds", theirImplicitEnds, 0)
+        Debug(logger, "")
+        Debug(logger, "Process implicitly issued events")
+        DebugSeq(logger, "generatorsOfImplicitEnds", generatorsOfImplicitEnds, 0)
+        DebugSeq(logger, "theirImplicitEnds", theirImplicitEnds, 0)
       }
 
       // Now, the previous and implicitly started must be our base for the following computation, so we create an
@@ -368,15 +381,13 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
         theirImplicitEnds,
         specialWorkingUserState,
         chargingReason,
-        billingMonthInfo,
-        clogSome
+        billingMonthInfo
       )
 
       workingUserState.walletEntries ++= specialWorkingUserState.walletEntries
       workingUserState.totalCredits    = specialWorkingUserState.totalCredits
-    }
+    }*/
 
-    clog.end()
     workingUserState
   }
 }