Implement Once behavior with the new scheme. Refactor in the process
authorChristos KK Loverdos <loverdos@gmail.com>
Wed, 22 Aug 2012 14:08:30 +0000 (17:08 +0300)
committerChristos KK Loverdos <loverdos@gmail.com>
Wed, 22 Aug 2012 14:11:08 +0000 (17:11 +0300)
27 files changed:
src/main/scala/gr/grnet/aquarium/Aquarium.scala
src/main/scala/gr/grnet/aquarium/AquariumAwareSkeleton.scala
src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala
src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala
src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala
src/main/scala/gr/grnet/aquarium/charging/ChargingInput.scala [deleted file]
src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala
src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala
src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala
src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala
src/main/scala/gr/grnet/aquarium/charging/state/AgreementHistory.scala
src/main/scala/gr/grnet/aquarium/charging/state/AgreementHistoryModel.scala [moved from src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorAliases.scala with 81% similarity]
src/main/scala/gr/grnet/aquarium/charging/state/ResourceInstanceChargingState.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/charging/state/ResourceInstanceChargingStateModel.scala [moved from src/main/scala/gr/grnet/aquarium/logic/accounting/algorithm/CostPolicyAlgorithmCompiler.scala with 74% similarity]
src/main/scala/gr/grnet/aquarium/charging/state/ResourcesChargingState.scala [moved from src/main/scala/gr/grnet/aquarium/logic/accounting/algorithm/ExecutableChargingBehaviorAlgorithm.scala with 71% similarity]
src/main/scala/gr/grnet/aquarium/charging/state/StdUserState.scala
src/main/scala/gr/grnet/aquarium/charging/state/UserStateModel.scala
src/main/scala/gr/grnet/aquarium/charging/state/UserStateModelSkeleton.scala
src/main/scala/gr/grnet/aquarium/charging/state/WorkingAgreementHistory.scala
src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/charging/state/WorkingUserState.scala
src/main/scala/gr/grnet/aquarium/computation/TimeslotComputations.scala
src/main/scala/gr/grnet/aquarium/connector/handler/IMEventPayloadHandler.scala
src/main/scala/gr/grnet/aquarium/policy/FullPriceTable.scala
src/main/scala/gr/grnet/aquarium/store/memory/MemStoreProvider.scala
src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBUserState.scala

index d078e76..fe606fd 100644 (file)
@@ -294,21 +294,19 @@ final class Aquarium(env: Env) extends Lifecycle with Loggable {
   }
 
   def chargingBehaviorOf(resourceType: ResourceType): ChargingBehavior = {
+    // A resource type never changes charging behavior. By definition.
     val className = resourceType.chargingBehavior
     _chargingBehaviorMap.get(className) match {
       case Some(chargingBehavior) ⇒
         chargingBehavior
 
       case _ ⇒
-        // It does not matter if this is entered by multiple threads and more than one instance of the same class
-        // is created. The returned instance is not meant to be cached.
         try {
-          val chargingBehavior = newInstance[ChargingBehavior](className)
           _chargingBehaviorMap synchronized {
+            val chargingBehavior = newInstance[ChargingBehavior](className)
             _chargingBehaviorMap = _chargingBehaviorMap.updated(className, chargingBehavior)
+            chargingBehavior
           }
-
-          chargingBehavior
         }
         catch {
           case e: Exception ⇒
index 19978df..542e16a 100644 (file)
@@ -38,6 +38,7 @@ package gr.grnet.aquarium
 import gr.grnet.aquarium.service.event.AquariumCreatedEvent
 import com.google.common.eventbus.Subscribe
 import gr.grnet.aquarium.util.Loggable
+import gr.grnet.aquarium.util.LogHelpers.Debug
 
 /**
  *
@@ -45,13 +46,13 @@ import gr.grnet.aquarium.util.Loggable
  */
 
 trait AquariumAwareSkeleton extends AquariumAware { this: Loggable ⇒
-  @volatile private var _aquarium: Aquarium = null
+  private var _aquarium: Aquarium = _
 
   final protected def aquarium = _aquarium
 
   @Subscribe
   def awareOfAquarium(event: AquariumCreatedEvent) = {
     this._aquarium = event.aquarium
-    logger.debug("Aware of Aquarium: %s".format(this._aquarium))
+    Debug(logger, "Aware of Aquarium: %s", this._aquarium)
   }
 }
index 476f3ce..7588811 100644 (file)
@@ -37,38 +37,32 @@ package gr.grnet.aquarium.actor
 package service
 package user
 
-import gr.grnet.aquarium.actor._
-
-import gr.grnet.aquarium.actor.message.event.{ProcessResourceEvent, ProcessIMEvent}
-import gr.grnet.aquarium.actor.message.config.{InitializeUserActorState, AquariumPropertiesLoaded}
 import gr.grnet.aquarium.util.date.TimeHelpers
 import gr.grnet.aquarium.service.event.BalanceEvent
 import gr.grnet.aquarium.event.model.im.IMEventModel
-import message._
-import config.AquariumPropertiesLoaded
-import config.InitializeUserActorState
-import event.ProcessIMEvent
-import event.ProcessResourceEvent
+import gr.grnet.aquarium.actor.message.config.AquariumPropertiesLoaded
+import gr.grnet.aquarium.actor.message.config.InitializeUserActorState
+import gr.grnet.aquarium.actor.message.event.ProcessIMEvent
+import gr.grnet.aquarium.actor.message.event.ProcessResourceEvent
 import gr.grnet.aquarium.util.{LogHelpers, shortClassNameOf}
-import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
+import gr.grnet.aquarium.AquariumInternalError
 import gr.grnet.aquarium.computation.BillingMonthInfo
-import gr.grnet.aquarium.charging.state.UserStateBootstrap
-import gr.grnet.aquarium.charging.state.{WorkingAgreementHistory, WorkingUserState, UserStateModel}
+import gr.grnet.aquarium.charging.state.{WorkingUserState, UserStateModel}
 import gr.grnet.aquarium.charging.reason.{InitialUserActorSetup, RealtimeChargingReason}
-import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement}
+import gr.grnet.aquarium.policy.PolicyDefinedFullPriceTableRef
 import gr.grnet.aquarium.event.model.resource.{StdResourceEvent, ResourceEventModel}
-import message.GetUserBalanceRequest
-import message.GetUserBalanceResponse
-import message.GetUserBalanceResponseData
-import message.GetUserStateRequest
-import message.GetUserStateResponse
-import message.GetUserWalletRequest
-import message.GetUserWalletResponse
-import message.GetUserWalletResponseData
-import scala.Left
+import gr.grnet.aquarium.actor.message.GetUserBalanceRequest
+import gr.grnet.aquarium.actor.message.GetUserBalanceResponse
+import gr.grnet.aquarium.actor.message.GetUserBalanceResponseData
+import gr.grnet.aquarium.actor.message.GetUserStateRequest
+import gr.grnet.aquarium.actor.message.GetUserStateResponse
+import gr.grnet.aquarium.actor.message.GetUserWalletRequest
+import gr.grnet.aquarium.actor.message.GetUserWalletResponse
+import gr.grnet.aquarium.actor.message.GetUserWalletResponseData
+import gr.grnet.aquarium.actor.message.GetUserBillRequest
+import gr.grnet.aquarium.actor.message.GetUserBillResponse
+import gr.grnet.aquarium.actor.message.GetUserBillResponseData
 import gr.grnet.aquarium.charging.state.WorkingAgreementHistory
-import scala.Some
-import scala.Right
 import gr.grnet.aquarium.policy.StdUserAgreement
 import gr.grnet.aquarium.charging.state.UserStateBootstrap
 import gr.grnet.aquarium.charging.bill.BillEntry
@@ -364,6 +358,9 @@ class UserActor extends ReflectiveRoleableActor {
     }
 
     val now = TimeHelpers.nowMillis()
+    // TODO: Review this and its usage in user state.
+    // TODO: The assumption is that the resource set increases all the time,
+    // TODO: so the current map contains everything ever known (assuming we do not run backwards in time).
     val currentResourcesMap = aquarium.currentResourceTypesMap
     val chargingReason = RealtimeChargingReason(None, now)
 
index 7e80e69..2bd1364 100644 (file)
 package gr.grnet.aquarium.charging
 
 import gr.grnet.aquarium.policy.{ResourceType, EffectivePriceTable, FullPriceTable}
-import scala.collection.mutable
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
 import gr.grnet.aquarium.Aquarium
 import gr.grnet.aquarium.computation.BillingMonthInfo
-import gr.grnet.aquarium.charging.state.AgreementHistory
+import gr.grnet.aquarium.charging.state.{WorkingResourceInstanceChargingState, AgreementHistoryModel, WorkingResourcesChargingState}
 import gr.grnet.aquarium.charging.wallet.WalletEntry
-import com.ckkloverdos.key.TypedKeySkeleton
+import scala.collection.mutable
 
 /**
  * A charging behavior indicates how charging for a resource will be done
- * wrt the various states a resource instance can be.
+ * wrt the various states a resource instance can be in.
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 
 trait ChargingBehavior {
-  def alias: String
+  def selectorLabelsHierarchy: List[String]
+
+  /**
+   * Provides some initial charging details that will be part of the mutable charging state
+   * ([[gr.grnet.aquarium.charging.state.WorkingResourcesChargingState]]).
+   */
+  def initialChargingDetails: Map[String, Any]
 
-  def inputs: Set[ChargingInput]
+  def computeSelectorPath(
+      workingChargingBehaviorDetails: mutable.Map[String, Any],
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+      currentResourceEvent: ResourceEventModel,
+      referenceTimeslot: Timeslot,
+      totalCredits: Double
+  ): List[String]
 
   def selectEffectivePriceTable(
       fullPriceTable: FullPriceTable,
-      chargingData: mutable.Map[String, Any],
+      workingChargingBehaviorDetails: mutable.Map[String, Any],
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
       currentResourceEvent: ResourceEventModel,
       referenceTimeslot: Timeslot,
-      previousValue: Double,
-      totalCredits: Double,
-      oldAccumulatingAmount: Double,
-      newAccumulatingAmount: Double
+      totalCredits: Double
   ): EffectivePriceTable
 
-
   /**
    *
    * @return The number of wallet entries recorded and the new total credits
    */
-  def chargeResourceEvent(
+  def processResourceEvent(
       aquarium: Aquarium,
       currentResourceEvent: ResourceEventModel,
       resourceType: ResourceType,
       billingMonthInfo: BillingMonthInfo,
-      previousResourceEventOpt: Option[ResourceEventModel],
-      userAgreements: AgreementHistory,
-      chargingData: mutable.Map[String, Any],
+      workingResourcesChargingState: WorkingResourcesChargingState,
+      userAgreements: AgreementHistoryModel,
       totalCredits: Double,
       walletEntryRecorder: WalletEntry ⇒ Unit
   ): (Int, Double)
 
-  def supportsImplicitEvents: Boolean
-
-  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel): Boolean
-
-  def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccuredMillis: Long): ResourceEventModel
-}
-
-object ChargingBehavior {
-  final case class ChargingBehaviorKey[T: Manifest](override val name: String) extends TypedKeySkeleton[T](name)
+  def computeCreditsToSubtract(
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+      oldCredits: Double,
+      timeDeltaMillis: Long,
+      unitPrice: Double
+  ): (Double /* credits */, String /* explanation */)
 
   /**
-   * Keys used to save information between calls of `chargeResourceEvent`
+   * Given the charging state of a resource instance and the details of the incoming message, compute the new
+   * accumulating amount.
    */
-  object EnvKeys {
-    final val ResourceInstanceAccumulatingAmount = ChargingBehaviorKey[Double]("resource.instance.accumulating.amount")
-  }
-
-  def makeValueMapFor(
-      chargingBehavior: ChargingBehavior,
-      totalCredits: Double,
-      oldTotalAmount: Double,
-      newTotalAmount: Double,
-      timeDelta: Double,
-      previousValue: Double,
-      currentValue: Double,
-      unitPrice: Double
-  ): Map[ChargingInput, Any] = {
-
-    val inputs = chargingBehavior.inputs
-    var map = Map[ChargingInput, Any]()
-
-    if(inputs contains ChargingBehaviorNameInput) map += ChargingBehaviorNameInput -> chargingBehavior.alias
-    if(inputs contains TotalCreditsInput        ) map += TotalCreditsInput   -> totalCredits
-    if(inputs contains OldTotalAmountInput      ) map += OldTotalAmountInput -> oldTotalAmount
-    if(inputs contains NewTotalAmountInput      ) map += NewTotalAmountInput -> newTotalAmount
-    if(inputs contains TimeDeltaInput           ) map += TimeDeltaInput      -> timeDelta
-    if(inputs contains PreviousValueInput       ) map += PreviousValueInput  -> previousValue
-    if(inputs contains CurrentValueInput        ) map += CurrentValueInput   -> currentValue
-    if(inputs contains UnitPriceInput           ) map += UnitPriceInput      -> unitPrice
-
-    map
-  }
+  def computeNewAccumulatingAmount(
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+      eventDetails: Map[String, String]
+  ): Double
 }
index 993fe62..9a5551e 100644 (file)
@@ -41,16 +41,13 @@ import scala.collection.mutable
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
 import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
 import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable, UserAgreementModel, ResourceType}
-import com.ckkloverdos.key.TypedKey
 import gr.grnet.aquarium.util._
-import gr.grnet.aquarium.util.LogHelpers.Debug
 import gr.grnet.aquarium.util.date.TimeHelpers
 import gr.grnet.aquarium.charging.wallet.WalletEntry
 import gr.grnet.aquarium.computation.{TimeslotComputations, BillingMonthInfo}
-import gr.grnet.aquarium.charging.state.AgreementHistory
+import gr.grnet.aquarium.charging.state.{WorkingResourceInstanceChargingState, WorkingResourcesChargingState, AgreementHistoryModel}
 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
 import gr.grnet.aquarium.store.PolicyStore
-import gr.grnet.aquarium.charging.ChargingBehavior.EnvKeys
 
 /**
  * A charging behavior indicates how charging for a resource will be done
@@ -60,113 +57,80 @@ import gr.grnet.aquarium.charging.ChargingBehavior.EnvKeys
  */
 
 abstract class ChargingBehaviorSkeleton(
-    final val alias: String,
-    final val inputs: Set[ChargingInput],
-    final val selectorHierarchy: List[List[String]] = Nil
+    final val selectorLabelsHierarchy: List[String]
 ) extends ChargingBehavior with Loggable {
 
-  final val inputNames = inputs.map(_.name)
-
-  @inline private[this] def hrs(millis: Double) = {
+  protected def hrs(millis: Double) = {
     val hours = millis / 1000 / 60 / 60
     val roundedHours = hours
     roundedHours
   }
 
-  protected def computeCreditsToSubtract(
-      oldCredits: Double,
-      oldAccumulatingAmount: Double,
-      newAccumulatingAmount: Double,
-      timeDeltaMillis: Long,
-      previousValue: Double,
-      currentValue: Double,
-      unitPrice: Double,
-      details: Map[String, String]
-  ): (Double, String) = {
-    alias match {
-     case ChargingBehaviorAliases.continuous ⇒
-       val credits = hrs(timeDeltaMillis) * oldAccumulatingAmount * unitPrice
-       val explanation = "Time(%s) * OldTotal(%s) * Unit(%s)".format(
-         hrs(timeDeltaMillis),
-         oldAccumulatingAmount,
-         unitPrice
-       )
-
-       (credits, explanation)
-
-     case ChargingBehaviorAliases.vmtime ⇒
-       val credits = hrs(timeDeltaMillis) * unitPrice
-       val explanation = "Time(%s) * Unit(%s)".format(hrs(timeDeltaMillis), unitPrice)
-
-       (credits, explanation)
-
-     case ChargingBehaviorAliases.once ⇒
-       val credits = currentValue
-       val explanation = "Value(%s)".format(currentValue)
-
-       (credits, explanation)
-
-     case name ⇒
-       throw new AquariumInternalError("Cannot compute credit diff for charging behavior %s".format(name))
-    }
-  }
-
   protected def rcDebugInfo(rcEvent: ResourceEventModel) = {
     rcEvent.toDebugString
   }
 
-  protected def computeSelectorPath(
-      chargingData: mutable.Map[String, Any],
-      currentResourceEvent: ResourceEventModel,
-      referenceTimeslot: Timeslot,
-      previousValue: Double,
-      totalCredits: Double,
-      oldAccumulatingAmount: Double,
-      newAccumulatingAmount: Double
-  ): List[String]
+  protected def newWorkingResourceInstanceChargingState() = {
+    new WorkingResourceInstanceChargingState(
+      mutable.Map(),
+      Nil,
+      Nil,
+      0.0,
+      0.0,
+      0.0,
+      0.0
+    )
+  }
 
-  /**
-   *
-   * @param chargingData
-   * @param previousResourceEventOpt
-   * @param currentResourceEvent
-   * @param billingMonthInfo
-   * @param referenceTimeslot
-   * @param resourceType
-   * @param agreementByTimeslot
-   * @param previousValue
-   * @param totalCredits
-   * @param policyStore
-   * @param walletEntryRecorder
-   * @return The number of wallet entries recorded and the new total credits
-   */
-  protected def computeChargeslots(
-      chargingData: mutable.Map[String, Any],
-      previousResourceEventOpt: Option[ResourceEventModel],
-      currentResourceEvent: ResourceEventModel,
+  final protected def ensureWorkingState(
+      workingResourcesChargingState: WorkingResourcesChargingState,
+      resourceEvent: ResourceEventModel
+  ) {
+    ensureResourcesChargingStateDetails(workingResourcesChargingState.details)
+    ensureResourceInstanceChargingState(workingResourcesChargingState, resourceEvent)
+  }
+
+  protected def ensureResourcesChargingStateDetails(
+    details: mutable.Map[String, Any]
+  ) {}
+
+  protected def ensureResourceInstanceChargingState(
+      workingResourcesChargingState: WorkingResourcesChargingState,
+      resourceEvent: ResourceEventModel
+  ) {
+
+    val instanceID = resourceEvent.instanceID
+    val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance
+
+    stateOfResourceInstance.get(instanceID) match {
+      case None ⇒
+        stateOfResourceInstance(instanceID) = newWorkingResourceInstanceChargingState()
+
+      case _ ⇒
+    }
+  }
+
+  protected def computeWalletEntriesForNewEvent(
+      resourceEvent: ResourceEventModel,
+      resourceType: ResourceType,
       billingMonthInfo: BillingMonthInfo,
+      totalCredits: Double,
       referenceTimeslot: Timeslot,
-      resourceType: ResourceType,
       agreementByTimeslot: immutable.SortedMap[Timeslot, UserAgreementModel],
-      previousValue: Double,
-      totalCredits: Double,
+      workingResourcesChargingStateDetails: mutable.Map[String, Any],
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
       policyStore: PolicyStore,
       walletEntryRecorder: WalletEntry ⇒ Unit
   ): (Int, Double) = {
 
-    val currentValue = currentResourceEvent.value
-    val userID = currentResourceEvent.userID
-    val currentDetails = currentResourceEvent.details
-
-    var _oldAccumulatingAmount = getChargingData(
-      chargingData,
-      EnvKeys.ResourceInstanceAccumulatingAmount
-    ).getOrElse(getResourceInstanceInitialAmount)
+    val userID = resourceEvent.userID
+    val resourceEventDetails = resourceEvent.details
 
     var _oldTotalCredits = totalCredits
 
-    var _newAccumulatingAmount = this.computeNewAccumulatingAmount(_oldAccumulatingAmount, currentValue, currentDetails)
-    setChargingData(chargingData, EnvKeys.ResourceInstanceAccumulatingAmount, _newAccumulatingAmount)
+    var _newAccumulatingAmount = computeNewAccumulatingAmount(workingResourceInstanceChargingState, resourceEventDetails)
+    // It will also update the old one inside the data structure.
+    workingResourceInstanceChargingState.setNewAccumulatingAmount(_newAccumulatingAmount)
 
     val policyByTimeslot = policyStore.loadAndSortPoliciesWithin(
       referenceTimeslot.from.getTime,
@@ -176,19 +140,16 @@ abstract class ChargingBehaviorSkeleton(
     val effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable = fullPriceTable ⇒ {
       this.selectEffectivePriceTable(
         fullPriceTable,
-        chargingData,
-        currentResourceEvent,
+        workingResourcesChargingStateDetails,
+        workingResourceInstanceChargingState,
+        resourceEvent,
         referenceTimeslot,
-        previousValue,
-        totalCredits,
-        _oldAccumulatingAmount,
-        _newAccumulatingAmount
+        totalCredits
       )
     }
 
     val initialChargeslots = TimeslotComputations.computeInitialChargeslots(
       referenceTimeslot,
-      resourceType,
       policyByTimeslot,
       agreementByTimeslot,
       effectivePriceTableSelector
@@ -199,14 +160,10 @@ abstract class ChargingBehaviorSkeleton(
         val timeDeltaMillis = stopMillis - startMillis
 
         val (creditsToSubtract, explanation) = this.computeCreditsToSubtract(
-          _oldTotalCredits,       // FIXME ??? Should recalculate ???
-          _oldAccumulatingAmount, // FIXME ??? Should recalculate ???
-          _newAccumulatingAmount, // FIXME ??? Should recalculate ???
+          workingResourceInstanceChargingState,
+          _oldTotalCredits, // FIXME ??? Should recalculate ???
           timeDeltaMillis,
-          previousValue,
-          currentValue,
-          unitPrice,
-          currentDetails
+          unitPrice
         )
 
         val newChargeslot = chargeslot.copyWithCreditsToSubtract(creditsToSubtract, explanation)
@@ -214,7 +171,7 @@ abstract class ChargingBehaviorSkeleton(
     }
 
     if(fullChargeslots.length == 0) {
-      throw new AquariumInternalError("No chargeslots computed for resource event %s".format(currentResourceEvent.id))
+      throw new AquariumInternalError("No chargeslots computed for resource event %s".format(resourceEvent.id))
     }
 
     val sumOfCreditsToSubtract = fullChargeslots.map(_.creditsToSubtract).sum
@@ -230,9 +187,9 @@ abstract class ChargingBehaviorSkeleton(
       billingMonthInfo.year,
       billingMonthInfo.month,
       fullChargeslots,
-      previousResourceEventOpt.map(List(_, currentResourceEvent)).getOrElse(List(currentResourceEvent)),
+      resourceEvent :: workingResourceInstanceChargingState.previousEvents,
       resourceType,
-      currentResourceEvent.isSynthetic
+      resourceEvent.isSynthetic
     )
 
     logger.debug("newWalletEntry = {}", newWalletEntry.toJsonString)
@@ -242,50 +199,22 @@ abstract class ChargingBehaviorSkeleton(
     (1, newTotalCredits)
   }
 
-  protected def removeChargingData[T: Manifest](
-      chargingData: mutable.Map[String, Any],
-      envKey: TypedKey[T]
-  ) = {
-
-    chargingData.remove(envKey.name).asInstanceOf[Option[T]]
-  }
-
-  protected def getChargingData[T: Manifest](
-      chargingData: mutable.Map[String, Any],
-      envKey: TypedKey[T]
-  ) = {
-
-    chargingData.get(envKey.name).asInstanceOf[Option[T]]
-  }
-
-  protected def setChargingData[T: Manifest](
-      chargingData: mutable.Map[String, Any],
-      envKey: TypedKey[T],
-      value: T
-  ) = {
-
-    chargingData(envKey.name) = value
-  }
 
   def selectEffectivePriceTable(
       fullPriceTable: FullPriceTable,
-      chargingData: mutable.Map[String, Any],
+      workingChargingBehaviorDetails: mutable.Map[String, Any],
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
       currentResourceEvent: ResourceEventModel,
       referenceTimeslot: Timeslot,
-      previousValue: Double,
-      totalCredits: Double,
-      oldAccumulatingAmount: Double,
-      newAccumulatingAmount: Double
+      totalCredits: Double
   ): EffectivePriceTable = {
 
     val selectorPath = computeSelectorPath(
-      chargingData,
+      workingChargingBehaviorDetails,
+      workingResourceInstanceChargingState,
       currentResourceEvent,
       referenceTimeslot,
-      previousValue,
-      totalCredits,
-      oldAccumulatingAmount,
-      newAccumulatingAmount
+      totalCredits
     )
 
     fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource)
@@ -295,30 +224,21 @@ abstract class ChargingBehaviorSkeleton(
    * A generic implementation for charging a resource event.
    * TODO: Ditch this in favor of completely ahdoc behaviors.
    *
-   * @param aquarium
-   * @param currentResourceEvent
-   * @param resourceType
-   * @param billingMonthInfo
-   * @param previousResourceEventOpt
-   * @param userAgreements
-   * @param chargingData
-   * @param totalCredits
-   * @param walletEntryRecorder
    * @return The number of wallet entries recorded and the new total credits
    */
-  def chargeResourceEvent(
+  def processResourceEvent(
       aquarium: Aquarium,
       currentResourceEvent: ResourceEventModel,
       resourceType: ResourceType,
       billingMonthInfo: BillingMonthInfo,
-      previousResourceEventOpt: Option[ResourceEventModel],
-      userAgreements: AgreementHistory,
-      chargingData: mutable.Map[String, Any],
+      workingResourcesChargingState: WorkingResourcesChargingState,
+      userAgreements: AgreementHistoryModel,
       totalCredits: Double,
       walletEntryRecorder: WalletEntry ⇒ Unit
   ): (Int, Double) = {
+    (0,0)
 
-    val currentResourceEventDebugInfo = rcDebugInfo(currentResourceEvent)
+    /*val currentResourceEventDebugInfo = rcDebugInfo(currentResourceEvent)
 
     val isBillable = this.isBillableEvent(currentResourceEvent)
     val retval = if(!isBillable) {
@@ -393,7 +313,7 @@ abstract class ChargingBehaviorSkeleton(
           Timeslot(currentResourceEvent.occurredMillis, currentResourceEvent.occurredMillis + 1),
           resourceType,
           userAgreements.agreementByTimeslot,
-          this.getResourceInstanceUndefinedAmount,
+          0.0,
           totalCredits,
           aquarium.policyStore,
           walletEntryRecorder
@@ -401,112 +321,21 @@ abstract class ChargingBehaviorSkeleton(
       }
     }
 
-    retval
-  }
-
-  /**
-   * Generate a map where the key is a [[gr.grnet.aquarium.charging.ChargingInput]]
-   * and the value the respective value. This map will be used to do the actual credit charge calculation
-   * by the respective algorithm.
-   *
-   * Values are obtained from a corresponding context, which is provided by the parameters. We assume that this context
-   * has been validated before the call to `makeValueMap` is made.
-   *
-   * @param totalCredits   the value for [[gr.grnet.aquarium.charging.TotalCreditsInput.]]
-   * @param oldTotalAmount the value for [[gr.grnet.aquarium.charging.OldTotalAmountInput]]
-   * @param newTotalAmount the value for [[gr.grnet.aquarium.charging.NewTotalAmountInput]]
-   * @param timeDelta      the value for [[gr.grnet.aquarium.charging.TimeDeltaInput]]
-   * @param previousValue  the value for [[gr.grnet.aquarium.charging.PreviousValueInput]]
-   * @param currentValue   the value for [[gr.grnet.aquarium.charging.CurrentValueInput]]
-   * @param unitPrice      the value for [[gr.grnet.aquarium.charging.UnitPriceInput]]
-   *
-   * @return a map from [[gr.grnet.aquarium.charging.ChargingInput]]s to respective values.
-   */
-  def makeValueMap(
-      totalCredits: Double,
-      oldTotalAmount: Double,
-      newTotalAmount: Double,
-      timeDelta: Double,
-      previousValue: Double,
-      currentValue: Double,
-      unitPrice: Double
-  ): Map[ChargingInput, Any] = {
-
-    ChargingBehavior.makeValueMapFor(
-      this,
-      totalCredits,
-      oldTotalAmount,
-      newTotalAmount,
-      timeDelta,
-      previousValue,
-      currentValue,
-      unitPrice)
+    retval*/
   }
 
-  def needsPreviousEventForCreditAndAmountCalculation: Boolean = {
-    // If we need any variable that is related to the previous event
-    // then we do need a previous event
-    inputs.exists(_.isDirectlyRelatedToPreviousEvent)
-  }
 
   /**
-   * Given the old amount of a resource instance, the value arriving in a new resource event and the new details,
-   * compute the new instance amount.
-   *
-   * Note that the `oldAmount` does not make sense for all types of [[gr.grnet.aquarium.charging.ChargingBehavior]],
-   * in which case it is ignored.
-   *
-   * @param oldAccumulatingAmount     the old accumulating amount
-   * @param newEventValue the value contained in a newly arrived
-   *                      [[gr.grnet.aquarium.event.model.resource.ResourceEventModel]]
-   * @param newDetails       the `details` of the newly arrived
-   *                      [[gr.grnet.aquarium.event.model.resource.ResourceEventModel]]
-   * @return
+   * Given the charging state of a resource instance and the details of the incoming message, compute the new
+   * accumulating amount.
    */
   def computeNewAccumulatingAmount(
-      oldAccumulatingAmount: Double,
-      newEventValue: Double,
-      newDetails: Map[String, String]
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+      eventDetails: Map[String, String]
   ): Double
 
-  /**
-   * The initial amount.
-   */
-  def getResourceInstanceInitialAmount: Double
-
-  /**
-   * The amount used when no amount is meant to be relevant.
-   *
-   * For example, when there is no need for a previous event but an API requires the amount of the previous event.
-   *
-   * Normally, this value will never be used by client code (= charge computation code).
-   */
-  def getResourceInstanceUndefinedAmount: Double = Double.NaN
-
-  /**
-   * An event carries enough info to characterize it as billable or not.
-   *
-   * Typically all events are billable by default and indeed this is the default implementation
-   * provided here.
-   *
-   * The only exception to the rule is ON events for [[gr.grnet.aquarium.charging.VMChargingBehavior]].
-   */
-  def isBillableEvent(event: ResourceEventModel): Boolean = true
-
-  /**
-   * This is called when we have the very first event for a particular resource instance, and we want to know
-   * if it is billable or not.
-   */
-  def isBillableFirstEvent(event: ResourceEventModel): Boolean
-
-  def mustGenerateDummyFirstEvent: Boolean
-
-  def dummyFirstEventValue: Double = 0.0 // FIXME read from configuration
 
   def constructDummyFirstEventFor(actualFirst: ResourceEventModel, newOccurredMillis: Long): ResourceEventModel = {
-    if(!mustGenerateDummyFirstEvent) {
-      throw new AquariumInternalError("constructDummyFirstEventFor() Not compliant with %s", this)
-    }
 
     val newDetails = Map(
       ResourceEventModel.Names.details_aquarium_is_synthetic   -> "true",
@@ -515,22 +344,6 @@ abstract class ChargingBehaviorSkeleton(
       ResourceEventModel.Names.details_aquarium_reference_event_id_in_store -> actualFirst.stringIDInStoreOrEmpty
     )
 
-    actualFirst.withDetailsAndValue(newDetails, dummyFirstEventValue, newOccurredMillis)
+    actualFirst.withDetailsAndValue(newDetails, 0.0, newOccurredMillis)
   }
-
-  /**
-   * There are resources (cost policies) for which implicit events must be generated at the end of the billing period
-   * and also at the beginning of the next one. For these cases, this method must return `true`.
-   *
-   * The motivating example comes from the [[gr.grnet.aquarium.charging.VMChargingBehavior]] for which we
-   * must implicitly assume `OFF` events at the end of the billing period and `ON` events at the beginning of the next
-   * one.
-   *
-   */
-  def supportsImplicitEvents: Boolean
-
-  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel): Boolean
-
-  @throws(classOf[Exception])
-  def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long): ResourceEventModel
 }
diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingInput.scala b/src/main/scala/gr/grnet/aquarium/charging/ChargingInput.scala
deleted file mode 100644 (file)
index 5d9a8f6..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2011-2012 GRNET S.A. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- *   1. Redistributions of source code must retain the above
- *      copyright notice, this list of conditions and the following
- *      disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following
- *      disclaimer in the documentation and/or other materials
- *      provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
- * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
- * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * The views and conclusions contained in the software and
- * documentation are those of the authors and should not be
- * interpreted as representing official policies, either expressed
- * or implied, of GRNET S.A.
- */
-
-package gr.grnet.aquarium.charging
-
-/**
- * An input that is used in a charging function.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-
-sealed abstract class ChargingInput(
-    val name: String,
-    val isDirectlyRelatedToPreviousEvent: Boolean = false,
-    val isDirectlyRelatedToCurrentEvent: Boolean = false
-)
-
-/**
- * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the name of the cost
- * policy for which a cost computation applies.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case object ChargingBehaviorNameInput extends ChargingInput("chargingBehaviorName")
-
-/**
- * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the total credits.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case object TotalCreditsInput extends ChargingInput("totalCredits")
-
-/**
- * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the old total (accumulating)
- * amount, that is the resource amount before taking into account a new resource event.
- * For example, in the case of `diskspace`, this is the total diskspace used by a user.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case object OldTotalAmountInput extends ChargingInput("oldTotalAmount")
-
-/**
- * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the new total (accumulating)
- * amount, that is the resource amount after taking into account a new resource event.
- * For example, in the case of `diskspace`, this is the total diskspace used by a user.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case object NewTotalAmountInput extends ChargingInput("newTotalAmount")
-
-/**
- * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the time delta between two
- * consecutive resource events of the same type (same `resource` and `instanceID`). Time is measured in milliseconds.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case object TimeDeltaInput extends ChargingInput("timeDelta", true, true)
-
-/**
- * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the `value` of the previous
- * [[gr.grnet.aquarium.event.model.resource.ResourceEventModel]].
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case object PreviousValueInput extends ChargingInput("previousValue", true, false)
-
-/**
- * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the `value` of the current
- * [[gr.grnet.aquarium.event.model.resource.ResourceEventModel]].
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case object CurrentValueInput extends ChargingInput("currentValue", false, true)
-
-/**
- * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the unit price.
- * 
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case object UnitPriceInput extends ChargingInput("unitPrice")
-
-case object DetailsInput extends ChargingInput("details")
-
-
-
-
index d87035e..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}
 import gr.grnet.aquarium.util.LogHelpers.Debug
 import gr.grnet.aquarium.util.LogHelpers.Warn
-import gr.grnet.aquarium.util.LogHelpers.DebugSeq
 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}
 
 /**
@@ -59,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
 
 
@@ -94,7 +93,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
         true,
         billingMonthInfo.year,
         billingMonthInfo.month,
-        None
+        ""
       )
 
       // We always save the state when it is a full month billing
@@ -176,7 +175,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
     }
   }
   /**
-   * Processes one resource event and computes relevant charges.
+   * Processes one resource event and computes relevant, incremental charges.
    *
    * @param resourceEvent
    * @param workingUserState
@@ -189,37 +188,53 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       chargingReason: ChargingReason,
       billingMonthInfo: BillingMonthInfo,
       updateLatestMillis: Boolean
-  ): Unit = {
+  ): 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
+
+      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 (walletEntriesCount, newTotalCredits) = chargingBehavior.chargeResourceEvent(
+    val m0 = TimeHelpers.nowMillis()
+    val (walletEntriesCount, newTotalCredits) = chargingBehavior.processResourceEvent(
       aquarium,
       resourceEvent,
       resourceType,
       billingMonthInfo,
-      workingUserState.previousEventOfResourceInstance.get(resourceAndInstanceInfo),
-      workingUserState.workingAgreementHistory.toAgreementHistory,
-      workingUserState.getChargingDataForResourceEvent(resourceAndInstanceInfo),
+      workingResourcesState,
+      workingUserState.workingAgreementHistory,
       workingUserState.totalCredits,
       workingUserState.walletEntries += _
     )
+    val m1 = TimeHelpers.nowMillis()
 
     if(updateLatestMillis) {
-      workingUserState.latestUpdateMillis = TimeHelpers.nowMillis()
+      workingUserState.latestUpdateMillis = m1
     }
 
     workingUserState.updateLatestResourceEventOccurredMillis(resourceEvent.occurredMillis)
-    workingUserState.previousEventOfResourceInstance(resourceAndInstanceInfo) = resourceEvent
     workingUserState.totalCredits = newTotalCredits
+
+    true
   }
 
   def processResourceEvents(
@@ -341,7 +356,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       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(
@@ -371,7 +386,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
 
       workingUserState.walletEntries ++= specialWorkingUserState.walletEntries
       workingUserState.totalCredits    = specialWorkingUserState.totalCredits
-    }
+    }*/
 
     workingUserState
   }
index 0dcfeba..18ff42c 100644 (file)
 package gr.grnet.aquarium.charging
 
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
-import scala.collection.mutable
 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
+import gr.grnet.aquarium.policy.ResourceType
+import gr.grnet.aquarium.charging.state.{AgreementHistoryModel, WorkingResourcesChargingState, WorkingResourceInstanceChargingState}
+import scala.collection.mutable
+import gr.grnet.aquarium.Aquarium
+import gr.grnet.aquarium.computation.BillingMonthInfo
+import gr.grnet.aquarium.charging.wallet.WalletEntry
 
 /**
  * In practice a resource usage will be charged for the total amount of usage
@@ -47,64 +52,66 @@ import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
-final class ContinuousChargingBehavior
-    extends ChargingBehaviorSkeleton(
-      ChargingBehaviorAliases.continuous,
-      Set(ChargingBehaviorNameInput, UnitPriceInput, OldTotalAmountInput, TimeDeltaInput)) {
+final class ContinuousChargingBehavior extends ChargingBehaviorSkeleton(Nil) {
 
-  protected def computeSelectorPath(
-      chargingData: mutable.Map[String, Any],
-      currentResourceEvent: ResourceEventModel,
-      referenceTimeslot: Timeslot,
-      previousValue: Double,
-      totalCredits: Double,
-      oldAccumulatingAmount: Double,
-      newAccumulatingAmount: Double
-  ): List[String] = {
-    Nil
-  }
+  def computeCreditsToSubtract(
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+      oldCredits: Double,
+      timeDeltaMillis: Long,
+      unitPrice: Double
+  ): (Double /* credits */, String /* explanation */) = {
 
-  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
-    // If the total is in the details, get it, or else compute it
-    details.get("total") match {
-      case Some(total) ⇒
-        total.toDouble
+    val oldAccumulatingAmount = workingResourceInstanceChargingState.oldAccumulatingAmount
+    val credits = hrs(timeDeltaMillis) * oldAccumulatingAmount * unitPrice
+    val explanation = "Time(%s) * OldTotal(%s) * Unit(%s)".format(
+      hrs(timeDeltaMillis),
+      oldAccumulatingAmount,
+      unitPrice
+    )
 
-      case _ ⇒
-        oldAmount + newEventValue
-    }
-  }
+    (credits, explanation)
 
-  def getResourceInstanceInitialAmount: Double = {
-    0.0
   }
 
-  /**
-   * This is called when we have the very first event for a particular resource instance, and we want to know
-   * if it is billable or not.
-   */
-  def isBillableFirstEvent(event: ResourceEventModel) = {
-    true
+  def computeSelectorPath(
+      workingChargingBehaviorDetails: mutable.Map[String, Any],
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+      currentResourceEvent: ResourceEventModel,
+      referenceTimeslot: Timeslot,
+      totalCredits: Double
+  ): List[String] = {
+    Nil
   }
 
-  def mustGenerateDummyFirstEvent = true
-
-  def supportsImplicitEvents = {
-    true
-  }
+  def initialChargingDetails: Map[String, Any] = Map()
 
-  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
-    true
+  def computeNewAccumulatingAmount(
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+      eventDetails: Map[String, String]
+  ): Double = {
+    workingResourceInstanceChargingState.oldAccumulatingAmount +
+    workingResourceInstanceChargingState.currentValue
   }
 
   def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
-    assert(supportsImplicitEvents && mustConstructImplicitEndEventFor(resourceEvent))
-
     val details = resourceEvent.details
     val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details)
 
     resourceEvent.withDetails(newDetails, newOccurredMillis)
   }
+
+  override def processResourceEvent(
+       aquarium: Aquarium,
+       currentResourceEvent: ResourceEventModel,
+       resourceType: ResourceType,
+       billingMonthInfo: BillingMonthInfo,
+       workingResourcesChargingState: WorkingResourcesChargingState,
+       userAgreements: AgreementHistoryModel,
+       totalCredits: Double,
+       walletEntryRecorder: WalletEntry ⇒ Unit
+   ): (Int, Double) = {
+    (0,0)
+  }
 }
 
 object ContinuousChargingBehavior {
index cbb66bc..c1637ac 100644 (file)
 package gr.grnet.aquarium.charging
 
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
-import gr.grnet.aquarium.AquariumException
+import gr.grnet.aquarium.{Aquarium, AquariumException}
 import scala.collection.mutable
 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
-import gr.grnet.aquarium.policy.FullPriceTable
+import gr.grnet.aquarium.policy.{ResourceType, FullPriceTable}
+import gr.grnet.aquarium.computation.BillingMonthInfo
+import gr.grnet.aquarium.charging.state.{WorkingResourceInstanceChargingState, AgreementHistoryModel, WorkingResourcesChargingState}
+import gr.grnet.aquarium.charging.wallet.WalletEntry
 
 /**
  * A charging behavior for which resource events just carry a credit amount that will be added to the total one.
@@ -48,44 +51,77 @@ import gr.grnet.aquarium.policy.FullPriceTable
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
-final class OnceChargingBehavior
-    extends ChargingBehaviorSkeleton(
-      ChargingBehaviorAliases.once,
-      Set(ChargingBehaviorNameInput, CurrentValueInput)) {
+final class OnceChargingBehavior extends ChargingBehaviorSkeleton(Nil) {
+  def computeCreditsToSubtract(
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+      oldCredits: Double,
+      timeDeltaMillis: Long,
+      unitPrice: Double
+  ): (Double /* credits */, String /* explanation */) = {
 
-  protected def computeSelectorPath(
-      chargingData: mutable.Map[String, Any],
+    val currentValue = workingResourceInstanceChargingState.currentValue
+    val credits = currentValue
+    val explanation = "Value(%s)".format(currentValue)
+
+    (credits, explanation)
+  }
+
+  def computeSelectorPath(
+      workingChargingBehaviorDetails: mutable.Map[String, Any],
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
       currentResourceEvent: ResourceEventModel,
       referenceTimeslot: Timeslot,
-      previousValue: Double,
-      totalCredits: Double,
-      oldAccumulatingAmount: Double,
-      newAccumulatingAmount: Double
+      totalCredits: Double
   ): List[String] = {
     List(FullPriceTable.DefaultSelectorKey)
   }
-  /**
-   * This is called when we have the very first event for a particular resource instance, and we want to know
-   * if it is billable or not.
-   */
-  def isBillableFirstEvent(event: ResourceEventModel) = {
-    true
-  }
 
-  def mustGenerateDummyFirstEvent = false // no need to
+  override def processResourceEvent(
+      aquarium: Aquarium,
+      resourceEvent: ResourceEventModel,
+      resourceType: ResourceType,
+      billingMonthInfo: BillingMonthInfo,
+      workingResourcesChargingState: WorkingResourcesChargingState,
+      userAgreements: AgreementHistoryModel,
+      totalCredits: Double,
+      walletEntryRecorder: WalletEntry ⇒ Unit
+  ): (Int, Double) = {
+    // The credits are given in the value
+    // But we cannot just apply them, since we also need to take into account the unit price.
+    // Normally, the unit price is 1.0 but we have the flexibility to allow more stuff).
 
-  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]) = {
-    oldAmount
-  }
+    val instanceID = resourceEvent.instanceID
+    val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance
+
+    // 0. Ensure proper state per resource and per instance
+    ensureWorkingState(workingResourcesChargingState, resourceEvent)
 
-  def getResourceInstanceInitialAmount = 0.0
+    // 1. Find the unit price at the moment of the event
 
-  def supportsImplicitEvents = false
+    val workingResourcesChargingStateDetails = workingResourcesChargingState.details
+    val workingResourceInstanceChargingState = stateOfResourceInstance(instanceID)
+
+    computeWalletEntriesForNewEvent(
+      resourceEvent,
+      resourceType,
+      billingMonthInfo,
+      totalCredits,
+      Timeslot(resourceEvent.occurredMillis, resourceEvent.occurredMillis + 1), // single point in time
+      userAgreements.agreementByTimeslot,
+      workingResourcesChargingStateDetails,
+      workingResourceInstanceChargingState,
+      aquarium.policyStore,
+      walletEntryRecorder
+    )
+  }
 
-  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = false
+  def initialChargingDetails: Map[String, Any] = Map()
 
-  def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, occurredMillis: Long) = {
-    throw new AquariumException("constructImplicitEndEventFor() Not compliant with %s".format(this))
+  def computeNewAccumulatingAmount(
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+      eventDetails: Map[String, String]
+  ): Double = {
+    workingResourceInstanceChargingState.oldAccumulatingAmount
   }
 }
 
index 0f43afb..a6b70ff 100644 (file)
 
 package gr.grnet.aquarium.charging
 
-import gr.grnet.aquarium.AquariumInternalError
+import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
-import scala.collection.mutable
 import VMChargingBehavior.Selectors.Power
+import VMChargingBehavior.SelectorLabels.PowerStatus
+import gr.grnet.aquarium.policy.ResourceType
+import gr.grnet.aquarium.computation.BillingMonthInfo
+import gr.grnet.aquarium.charging.state.{WorkingResourceInstanceChargingState, WorkingResourcesChargingState, AgreementHistoryModel}
+import gr.grnet.aquarium.charging.wallet.WalletEntry
+import scala.collection.mutable
 
 /**
  * The new [[gr.grnet.aquarium.charging.ChargingBehavior]] for VMs usage.
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
-final class VMChargingBehavior
-    extends ChargingBehaviorSkeleton(
-      ChargingBehaviorAliases.vmtime,
-      Set(ChargingBehaviorNameInput, UnitPriceInput, TimeDeltaInput),
-      List(List(Power.powerOn, Power.powerOff))) {
-
-  protected def computeSelectorPath(
-     chargingData: mutable.Map[String, Any],
-     currentResourceEvent: ResourceEventModel,
-     referenceTimeslot: Timeslot,
-     previousValue: Double,
-     totalCredits: Double,
-     oldAccumulatingAmount: Double,
-     newAccumulatingAmount: Double
- ): List[String] = {
-    // FIXME
-    List(Power.powerOn) // compute prices for power-on state
-  }
-  /**
-   *
-   * @param oldAmount is ignored
-   * @param newEventValue
-   * @return
-   */
-  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
-    newEventValue
-  }
+final class VMChargingBehavior extends ChargingBehaviorSkeleton(List(PowerStatus)) {
+  def computeCreditsToSubtract(
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+      oldCredits: Double,
+      timeDeltaMillis: Long,
+      unitPrice: Double
+  ): (Double /* credits */, String /* explanation */) = {
 
-  def getResourceInstanceInitialAmount: Double = {
-    0.0
-  }
+    val credits = hrs(timeDeltaMillis) * unitPrice
+    val explanation = "Time(%s) * Unit(%s)".format(hrs(timeDeltaMillis), unitPrice)
 
-  override def isBillableEvent(event: ResourceEventModel) = {
-    // ON events do not contribute, only OFF ones.
-    VMChargingBehaviorValues.isOFFValue(event.value)
-  }
+    (credits, explanation)
 
-  /**
-   * This is called when we have the very first event for a particular resource instance, and we want to know
-   * if it is billable or not.
-   */
-  def isBillableFirstEvent(event: ResourceEventModel) = {
-    false
   }
 
-  def mustGenerateDummyFirstEvent = false // should be handled by the implicit OFFs
-
-  def supportsImplicitEvents = {
-    true
+  def computeSelectorPath(
+      workingChargingBehaviorDetails: mutable.Map[String, Any],
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+      currentResourceEvent: ResourceEventModel,
+      referenceTimeslot: Timeslot,
+      totalCredits: Double
+  ): List[String] = {
+    // FIXME
+    List(Power.powerOn) // compute prices for power-on state
   }
 
-  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
-    // If we have ON events with no OFF companions at the end of the billing period,
-    // then we must generate implicit OFF events.
-    VMChargingBehaviorValues.isONValue(resourceEvent.value)
+  def initialChargingDetails: Map[String, Any] = Map()
+
+  def computeNewAccumulatingAmount(
+      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+      eventDetails: Map[String, String]
+  ): Double = {
+    workingResourceInstanceChargingState.currentValue
   }
 
   def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
-    assert(supportsImplicitEvents && mustConstructImplicitEndEventFor(resourceEvent))
     assert(VMChargingBehaviorValues.isONValue(resourceEvent.value))
 
     val details = resourceEvent.details
@@ -117,9 +99,33 @@ final class VMChargingBehavior
   def constructImplicitStartEventFor(resourceEvent: ResourceEventModel) = {
     throw new AquariumInternalError("constructImplicitStartEventFor() Not compliant with %s".format(this))
   }
+
+  /**
+   *
+   * @return The number of wallet entries recorded and the new total credits
+   */
+  override def processResourceEvent(
+      aquarium: Aquarium,
+      currentResourceEvent: ResourceEventModel,
+      resourceType: ResourceType,
+      billingMonthInfo: BillingMonthInfo,
+      workingState: WorkingResourcesChargingState,
+      userAgreements: AgreementHistoryModel,
+      totalCredits: Double,
+      walletEntryRecorder: WalletEntry ⇒ Unit
+  ): (Int, Double) = {
+
+
+    (0,0)
+  }
+
 }
 
 object VMChargingBehavior {
+  object SelectorLabels {
+    final val PowerStatus = "Power Status (ON/OFF)"
+  }
+
   object Selectors {
     object Power {
       // When the VM is powered on
index 96b2a48..5572b9c 100644 (file)
@@ -45,7 +45,7 @@ import gr.grnet.aquarium.policy.UserAgreementModel
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 
-case class AgreementHistory(agreements: List[UserAgreementModel]) {
+case class AgreementHistory(agreements: List[UserAgreementModel]) extends AgreementHistoryModel {
   def toWorkingAgreementHistory = {
     (new WorkingAgreementHistory) ++ agreements
   }
  * or implied, of GRNET S.A.
  */
 
-package gr.grnet.aquarium.charging
+package gr.grnet.aquarium.charging.state
+
+import gr.grnet.aquarium.policy.UserAgreementModel
+import scala.collection.immutable
+import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
 
 /**
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
+trait AgreementHistoryModel {
+  def size = agreements.size
+
+  def agreements: Traversable[UserAgreementModel]
 
-object ChargingBehaviorAliases {
-  final val vmtime     = "vmtime"
-  final val continuous = "continuous"
-  final val once       = "once"
+  def agreementByTimeslot: immutable.SortedMap[Timeslot, UserAgreementModel]
 }
diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/ResourceInstanceChargingState.scala b/src/main/scala/gr/grnet/aquarium/charging/state/ResourceInstanceChargingState.scala
new file mode 100644 (file)
index 0000000..03d493b
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer in the documentation and/or other materials
+ *      provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and
+ * documentation are those of the authors and should not be
+ * interpreted as representing official policies, either expressed
+ * or implied, of GRNET S.A.
+ */
+
+package gr.grnet.aquarium.charging.state
+
+import scala.collection.mutable
+
+import gr.grnet.aquarium.event.model.resource.ResourceEventModel
+
+/**
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+case class ResourceInstanceChargingState(
+    details: Map[String, Any],
+    previousEvents: List[ResourceEventModel],
+    // the implicitly issued resource event at the beginning of the billing period.
+    implicitlyIssuedStartEvent: List[ResourceEventModel],
+    accumulatingAmount: Double,
+    oldAccumulatingAmount: Double,
+    previousValue: Double,
+    currentValue: Double
+) extends ResourceInstanceChargingStateModel {
+
+  def mutableDetails = mutable.Map(this.details.toSeq:_*)
+
+  def toWorkingResourceInstanceChargingState = {
+    new WorkingResourceInstanceChargingState(
+      details = mutableDetails,
+      previousEvents = this.previousEvents,
+      implicitlyIssuedStartEvent = this.implicitlyIssuedStartEvent,
+      accumulatingAmount = this.accumulatingAmount,
+      oldAccumulatingAmount = this.oldAccumulatingAmount,
+      previousValue = this.previousValue,
+      currentValue = this.currentValue
+    )
+  }
+}
  * or implied, of GRNET S.A.
  */
 
-package gr.grnet.aquarium.logic.accounting.algorithm
-
-import com.ckkloverdos.maybe.{Just, Maybe}
+package gr.grnet.aquarium.charging.state
 
+import gr.grnet.aquarium.event.model.resource.ResourceEventModel
 
 /**
- * Compiles the textual representation of a cost policy charging algorithm to an executable form.
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
+trait ResourceInstanceChargingStateModel {
+  def details: scala.collection.Map[String, Any]
+
+  def previousEvents: List[ResourceEventModel]
+
+  // the implicitly issued resource event at the beginning of the billing period.
+  def implicitlyIssuedStartEvent: List[ResourceEventModel]
+
+  // Always the new accumulating amount
+  def accumulatingAmount: Double
+
+  def oldAccumulatingAmount: Double
+
+  def previousValue: Double
 
-trait CostPolicyAlgorithmCompiler {
-  /**
-   * Compiles the textual representation of a cost policy charging algorithm to an executable form.
-   *
-   * @param definition the textual representation of the algorithm
-   * @return the executable form of the algorithm
-   */
-  def compile(definition: String): ExecutableChargingBehaviorAlgorithm
+  def currentValue: Double
 }
  * or implied, of GRNET S.A.
  */
 
-package gr.grnet.aquarium.logic.accounting.algorithm
-
-import gr.grnet.aquarium.charging.ChargingInput
+package gr.grnet.aquarium.charging.state
 
+import scala.collection.mutable
 
 /**
- * An charging algorithm in executable form.
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
-trait ExecutableChargingBehaviorAlgorithm extends (Map[ChargingInput, Any] ⇒ Double)
+case class ResourcesChargingState(
+    details: Map[String, Any],
+    stateOfResourceInstance: Map[String /* InstanceID */, ResourceInstanceChargingState]
+) {
+
+  def mutableDetails = mutable.Map(this.details.toSeq:_*)
+
+  def mutableStateOfResourceInstance = mutable.Map((
+      for((k, v) ← this.stateOfResourceInstance)
+        yield (k, v.toWorkingResourceInstanceChargingState)
+      ).toSeq: _*
+  )
+
+  def toWorkingResourcesChargingState = {
+    new WorkingResourcesChargingState(
+      details = mutableDetails,
+      stateOfResourceInstance = mutableStateOfResourceInstance
+    )
+  }
+}
index e1e594f..e75f47c 100644 (file)
@@ -36,7 +36,6 @@
 package gr.grnet.aquarium.charging.state
 
 import gr.grnet.aquarium.policy.UserAgreementModel
-import gr.grnet.aquarium.event.model.resource.ResourceEventModel
 import gr.grnet.aquarium.charging.wallet.WalletEntry
 import gr.grnet.aquarium.charging.reason.{InitialUserStateSetup, ChargingReason}
 import gr.grnet.aquarium.AquariumInternalError
@@ -58,10 +57,7 @@ final case class StdUserState(
     billingYear: Int,
     billingMonth: Int,
     chargingReason: ChargingReason,
-    previousResourceEvents: List[ResourceEventModel],
-    implicitlyIssuedStartEvents: List[ResourceEventModel],
-    accumulatingAmountOfResourceInstance: Map[String, Double],
-    chargingDataOfResourceInstance: Map[String, Map[String, Any]],
+    stateOfResources: Map[String, ResourcesChargingState],
     billingPeriodOutOfSyncResourceEventsCounter: Long,
     agreementHistory: AgreementHistory,
     walletEntries: List[WalletEntry]
@@ -122,9 +118,6 @@ final object StdUserState {
       bmi.year,
       bmi.month,
       chargingReason,
-      Nil,
-      Nil,
-      Map(),
       Map(),
       0L,
       AgreementHistory.initial(initialAgreement),
index 44ad509..dc00baa 100644 (file)
@@ -72,13 +72,7 @@ trait UserStateModel extends JsonSupport {
 
   def chargingReason: ChargingReason
 
-  def previousResourceEvents: List[ResourceEventModel]
-
-  def implicitlyIssuedStartEvents: List[ResourceEventModel]
-
-  def accumulatingAmountOfResourceInstance: Map[String, Double]
-
-  def chargingDataOfResourceInstance: Map[String, Map[String, Any]]
+  def stateOfResources: Map[String, ResourcesChargingState]
 
   def billingPeriodOutOfSyncResourceEventsCounter: Long
 
index 2bdf896..b0326a7 100644 (file)
@@ -36,7 +36,6 @@
 package gr.grnet.aquarium.charging.state
 
 import scala.collection.mutable
-import gr.grnet.aquarium.event.model.resource.ResourceEventModel
 import gr.grnet.aquarium.charging.wallet.WalletEntry
 import gr.grnet.aquarium.policy.ResourceType
 
@@ -47,33 +46,9 @@ import gr.grnet.aquarium.policy.ResourceType
  */
 
 abstract class UserStateModelSkeleton extends UserStateModel {
-  protected def mutableMap[Vin, Vout](
-      inputMap: Map[String, Vin],
-      vInOut: Vin ⇒ Vout
-  ): mutable.Map[(String, String), Vout] = {
-    val items = for {
-      (resourceAndInstanceID, vIn) ← inputMap.toSeq
-    } yield {
-      StdUserState.resourceAndInstanceIDOfString(resourceAndInstanceID) -> vInOut(vIn)
-    }
-
-    mutable.Map(items: _*)
-  }
-
-  protected def mutableAccumulatingAmountMap: mutable.Map[(String, String), Double] = {
-    mutableMap(accumulatingAmountOfResourceInstance, identity[Double])
-  }
-
-  protected def mutableChargingDataMap: mutable.Map[(String, String), mutable.Map[String, Any]] = {
-    mutableMap(chargingDataOfResourceInstance, (vIn: Map[String, Any]) ⇒ mutable.Map(vIn.toSeq: _*))
-  }
-
-  protected def mutableImplicitlyIssuedStartMap: mutable.Map[(String, String), ResourceEventModel] = {
-    mutable.Map(implicitlyIssuedStartEvents.map(rem ⇒ (rem.safeResource, rem.safeInstanceID) -> rem): _*)
-  }
-
-  protected def mutablePreviousEventsMap: mutable.Map[(String, String), ResourceEventModel] = {
-    mutable.Map(previousResourceEvents.map(rem ⇒ (rem.safeResource, rem.safeInstanceID) -> rem): _*)
+  protected def mutableStateOfChargingBehavior: mutable.Map[String, WorkingResourcesChargingState] = {
+    val contents = for((k, v) ← stateOfResources) yield (k, v.toWorkingResourcesChargingState)
+    mutable.Map(contents.toSeq: _*)
   }
 
   protected def mutableWalletEntries = {
@@ -92,10 +67,7 @@ abstract class UserStateModelSkeleton extends UserStateModel {
       this.parentIDInStore,
       this.chargingReason,
       resourceTypesMap,
-      mutablePreviousEventsMap,
-      mutableImplicitlyIssuedStartMap,
-      mutableAccumulatingAmountMap,
-      mutableChargingDataMap,
+      mutableStateOfChargingBehavior,
       this.totalCredits,
       mutableAgreementHistory,
       this.occurredMillis,
index 2039a25..80aeff5 100644 (file)
@@ -38,6 +38,7 @@ package gr.grnet.aquarium.charging.state
 import scala.collection.immutable
 import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement, UserAgreementModel}
 import gr.grnet.aquarium.util.json.JsonSupport
+import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
 
 /**
  *
@@ -46,9 +47,11 @@ import gr.grnet.aquarium.util.json.JsonSupport
 
 final case class WorkingAgreementHistory(
     var agreements: immutable.SortedSet[UserAgreementModel] = immutable.SortedSet[UserAgreementModel]()
-) extends JsonSupport {
+) extends AgreementHistoryModel with JsonSupport {
 
-  def size = agreements.size
+  def agreementByTimeslot: immutable.SortedMap[Timeslot, UserAgreementModel] = {
+    immutable.TreeMap(agreements.map(ag ⇒ (ag.timeslot, ag)).toSeq: _*)
+  }
 
   def setFrom(that: WorkingAgreementHistory): this.type = {
     this.agreements = that.agreements
diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala
new file mode 100644 (file)
index 0000000..7cb207a
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer in the documentation and/or other materials
+ *      provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and
+ * documentation are those of the authors and should not be
+ * interpreted as representing official policies, either expressed
+ * or implied, of GRNET S.A.
+ */
+
+package gr.grnet.aquarium.charging.state
+
+import scala.collection.mutable
+
+import gr.grnet.aquarium.event.model.resource.ResourceEventModel
+
+/**
+ * Working (mutable) state of a resource instance, that is a `(resourceType, instanceID)`.
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+final class WorkingResourceInstanceChargingState(
+    val details: mutable.Map[String, Any],
+    var previousEvents: List[ResourceEventModel],
+    // the implicitly issued resource event at the beginning of the billing period.
+    var implicitlyIssuedStartEvent: List[ResourceEventModel],
+    // Always the new accumulating amount
+    var accumulatingAmount: Double,
+    var oldAccumulatingAmount: Double,
+    var previousValue: Double,
+    var currentValue: Double
+) extends ResourceInstanceChargingStateModel {
+
+  def toResourceInstanceChargingState = {
+    new ResourceInstanceChargingState(
+      details = immutableDetails,
+      previousEvents = this.previousEvents,
+      implicitlyIssuedStartEvent = this.implicitlyIssuedStartEvent,
+      accumulatingAmount = this.accumulatingAmount,
+      oldAccumulatingAmount = this.oldAccumulatingAmount,
+      previousValue = this.previousValue,
+      currentValue = this.currentValue
+    )
+  }
+
+  def immutableDetails = this.details.toMap
+
+  def setNewAccumulatingAmount(amount: Double) {
+    this.oldAccumulatingAmount = this.accumulatingAmount
+    this.accumulatingAmount = amount
+  }
+
+  def setNewCurrentValue(value: Double) {
+    this.previousValue = this.currentValue
+    this.currentValue = value
+  }
+}
diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.scala b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.scala
new file mode 100644 (file)
index 0000000..ed9f56b
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer in the documentation and/or other materials
+ *      provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and
+ * documentation are those of the authors and should not be
+ * interpreted as representing official policies, either expressed
+ * or implied, of GRNET S.A.
+ */
+
+package gr.grnet.aquarium.charging.state
+
+import scala.collection.mutable
+
+/**
+ * Working (mutable state) for a resource instances of the same resource type.
+ *
+ * @param details Generic state related to the type of resource as a whole
+ * @param stateOfResourceInstance A map from `instanceID` to
+ *                                [[gr.grnet.aquarium.charging.state.WorkingResourceInstanceChargingState]].
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+final class WorkingResourcesChargingState(
+    val details: mutable.Map[String, Any],
+    val stateOfResourceInstance: mutable.Map[String, WorkingResourceInstanceChargingState]
+) {
+
+  def immutableDetails = Map(this.details.toSeq: _*)
+
+  def immutableStateOfResourceInstance = Map((
+      for((k, v) ← this.stateOfResourceInstance)
+        yield (k, v.toResourceInstanceChargingState)
+    ).toSeq:_*)
+
+  def toResourcesChargingState = {
+    ResourcesChargingState(
+      details = immutableDetails,
+      stateOfResourceInstance = immutableStateOfResourceInstance
+    )
+  }
+}
index 0bff90e..7341b84 100644 (file)
@@ -38,7 +38,6 @@ package gr.grnet.aquarium.charging.state
 import scala.collection.mutable
 import gr.grnet.aquarium.policy.ResourceType
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
-import gr.grnet.aquarium.computation.BillingMonthInfo
 import gr.grnet.aquarium.charging.reason.ChargingReason
 import gr.grnet.aquarium.util.json.JsonSupport
 import gr.grnet.aquarium.charging.ChargingBehavior
@@ -55,26 +54,13 @@ final class WorkingUserState(
     var parentUserStateIDInStore: Option[String],
     var chargingReason: ChargingReason,
     val resourceTypesMap: Map[String, ResourceType],
-
-    /**
-     * This is a collection of all the latest resource events.
-     * We want these in order to correlate incoming resource events with their previous (in `occurredMillis` time)
-     * ones. Will be updated on processing the next resource event.
-     */
-    val previousEventOfResourceInstance: mutable.Map[(String, String), ResourceEventModel],
-
-    /**
-     * the implicitly issued resource events at the beginning of the billing period.
-     */
-    val implicitlyIssuedStartEventOfResourceInstance: mutable.Map[(String, String), ResourceEventModel],
-    val accumulatingAmountOfResourceInstance: mutable.Map[(String, String), Double],
-    val chargingDataOfResourceInstance: mutable.Map[(String, String), mutable.Map[String, Any]],
+    val workingStateOfResources: mutable.Map[String /* resourceType.name */, WorkingResourcesChargingState],
     var totalCredits: Double,
     val workingAgreementHistory: WorkingAgreementHistory,
     var latestUpdateMillis: Long, // last update of this working user state
     var latestResourceEventOccurredMillis: Long,
     var billingPeriodOutOfSyncResourceEventsCounter: Long,
-    val walletEntries: mutable.ListBuffer[WalletEntry]
+    val walletEntries: mutable.ListBuffer[WalletEntry] // FIXME: not all in memory
 ) extends JsonSupport {
 
   def updateLatestResourceEventOccurredMillis(millis: Long): Unit = {
@@ -83,46 +69,24 @@ final class WorkingUserState(
     }
   }
 
-  private[this] def immutablePreviousResourceEvents: List[ResourceEventModel] = {
-    previousEventOfResourceInstance.valuesIterator.toList
-  }
-
-  private[this] def immutableImplicitlyIssuedStartEvents: List[ResourceEventModel] = {
-    implicitlyIssuedStartEventOfResourceInstance.valuesIterator.toList
-  }
-
-  private[this] def immutableAccumulatingAmountMap: Map[String, Double] = {
-    val items = for {
-      ((resource, instanceID), accumulatingAmount) ← accumulatingAmountOfResourceInstance.toSeq
-    } yield {
-      StdUserState.stringOfResourceAndInstanceID(resource, instanceID) -> accumulatingAmount
-    }
-
-    Map(items: _*)
-  }
-
-  private[this] def immutableChargingDataMap: Map[String, Map[String, Any]] = {
-    val items = for {
-      ((resource, instanceID), mapValue) ← chargingDataOfResourceInstance.toSeq
-    } yield {
-      StdUserState.stringOfResourceAndInstanceID(resource, instanceID) -> Map(mapValue.toSeq: _*)
-    }
-
-    Map(items: _*)
+  def immutableAgreementHistory = {
+    this.workingAgreementHistory.toAgreementHistory
   }
 
-  private[this] def immutableAgreementHistory = {
-    this.workingAgreementHistory.toAgreementHistory
+  def immutableChargingBehaviorState = {
+    val contents = for((k, v) ← this.workingStateOfResources) yield (k, v.toResourcesChargingState)
+    Map(contents.toSeq:_*)
   }
 
+  // TODO: Connect this user state to an originating parent working user state (if applicable) => new attribute
   def toUserState(
       isFullBillingMonth: Boolean,
       billingYear: Int,
       billingMonth: Int,
-      idOpt: Option[String]
+      id: String
    ) = {
     new StdUserState(
-      idOpt.getOrElse(""),
+      id,
       this.parentUserStateIDInStore,
       this.userID,
       this.latestUpdateMillis,
@@ -132,61 +96,58 @@ final class WorkingUserState(
       billingYear,
       billingMonth,
       this.chargingReason,
-      immutablePreviousResourceEvents,
-      immutableImplicitlyIssuedStartEvents,
-      immutableAccumulatingAmountMap,
-      immutableChargingDataMap,
+      immutableChargingBehaviorState,
       billingPeriodOutOfSyncResourceEventsCounter,
       immutableAgreementHistory,
       walletEntries.toList
     )
   }
 
-  def newForImplicitEndsAsPreviousEvents(
-      previousResourceEvents: mutable.Map[(String, String), ResourceEventModel]
-  ) = {
-
-    new WorkingUserState(
-      this.userID,
-      this.parentUserStateIDInStore,
-      this.chargingReason,
-      this.resourceTypesMap,
-      previousResourceEvents,
-      this.implicitlyIssuedStartEventOfResourceInstance,
-      this.accumulatingAmountOfResourceInstance,
-      this.chargingDataOfResourceInstance,
-      this.totalCredits,
-      this.workingAgreementHistory,
-      this.latestUpdateMillis,
-      this.latestResourceEventOccurredMillis,
-      this.billingPeriodOutOfSyncResourceEventsCounter,
-      this.walletEntries
-    )
-  }
+//  def newForImplicitEndsAsPreviousEvents(
+//      previousResourceEvents: mutable.Map[(String, String), ResourceEventModel]
+//  ) = {
+//
+//    new WorkingUserState(
+//      this.userID,
+//      this.parentUserStateIDInStore,
+//      this.chargingReason,
+//      this.resourceTypesMap,
+//      previousResourceEvents,
+//      this.implicitlyIssuedStartEventOfResourceInstance,
+//      this.accumulatingAmountOfResourceInstance,
+//      this.chargingDataOfResourceInstance,
+//      this.totalCredits,
+//      this.workingAgreementHistory,
+//      this.latestUpdateMillis,
+//      this.latestResourceEventOccurredMillis,
+//      this.billingPeriodOutOfSyncResourceEventsCounter,
+//      this.walletEntries
+//    )
+//  }
 
   def findResourceType(name: String): Option[ResourceType] = {
     resourceTypesMap.get(name)
   }
 
-  def getChargingDataForResourceEvent(resourceAndInstanceInfo: (String, String)): mutable.Map[String, Any] = {
-    chargingDataOfResourceInstance.get(resourceAndInstanceInfo) match {
-      case Some(map) ⇒
-        map
-
-      case None ⇒
-        val map = mutable.Map[String, Any]()
-        chargingDataOfResourceInstance(resourceAndInstanceInfo) = map
-        map
-
-    }
-  }
-
-  def setChargingDataForResourceEvent(
-      resourceAndInstanceInfo: (String, String),
-      data: mutable.Map[String, Any]
-  ): Unit = {
-    chargingDataOfResourceInstance(resourceAndInstanceInfo) = data
-  }
+//  def getChargingDataForResourceEvent(resourceAndInstanceInfo: (String, String)): mutable.Map[String, Any] = {
+//    chargingDataOfResourceInstance.get(resourceAndInstanceInfo) match {
+//      case Some(map) ⇒
+//        map
+//
+//      case None ⇒
+//        val map = mutable.Map[String, Any]()
+//        chargingDataOfResourceInstance(resourceAndInstanceInfo) = map
+//        map
+//
+//    }
+//  }
+
+//  def setChargingDataForResourceEvent(
+//      resourceAndInstanceInfo: (String, String),
+//      data: mutable.Map[String, Any]
+//  ): Unit = {
+//    chargingDataOfResourceInstance(resourceAndInstanceInfo) = data
+//  }
 
   /**
   * Find those events from `implicitlyIssuedStartEvents` and `previousResourceEvents` that will generate implicit
@@ -195,46 +156,46 @@ final class WorkingUserState(
   *
   * @see [[gr.grnet.aquarium.charging.ChargingBehavior]]
   */
- def findAndRemoveGeneratorsOfImplicitEndEvents(
-     chargingBehaviorOfResourceType: ResourceType ⇒ ChargingBehavior,
-     /**
-      * The `occurredMillis` that will be recorded in the synthetic implicit OFFs.
-      * Normally, this will be the end of a billing month.
-      */
-     newOccuredMillis: Long
- ): (List[ResourceEventModel], List[ResourceEventModel]) = {
-
-   val buffer = mutable.ListBuffer[(ResourceEventModel, ResourceEventModel)]()
-   val checkSet = mutable.Set[ResourceEventModel]()
-
-   def doItFor(map: mutable.Map[(String, String), ResourceEventModel]): Unit = {
-     val resourceEvents = map.valuesIterator
-     for {
-       resourceEvent ← resourceEvents
-       resourceType ← resourceTypesMap.get(resourceEvent.safeResource)
-       chargingBehavior = chargingBehaviorOfResourceType.apply(resourceType)
-     } {
-       if(chargingBehavior.supportsImplicitEvents) {
-         if(chargingBehavior.mustConstructImplicitEndEventFor(resourceEvent)) {
-           val implicitEnd = chargingBehavior.constructImplicitEndEventFor(resourceEvent, newOccuredMillis)
-
-           if(!checkSet.contains(resourceEvent)) {
-             checkSet.add(resourceEvent)
-             buffer append ((resourceEvent, implicitEnd))
-           }
-
-           // remove it anyway
-           map.remove((resourceEvent.safeResource, resourceEvent.safeInstanceID))
-         }
-       }
-     }
-   }
-
-   doItFor(previousEventOfResourceInstance) // we give priority for previous events
-   doItFor(implicitlyIssuedStartEventOfResourceInstance) // ... over implicitly issued ones ...
-
-   (buffer.view.map(_._1).toList, buffer.view.map(_._2).toList)
- }
+// def findAndRemoveGeneratorsOfImplicitEndEvents(
+//     chargingBehaviorOfResourceType: ResourceType ⇒ ChargingBehavior,
+//     /**
+//      * The `occurredMillis` that will be recorded in the synthetic implicit OFFs.
+//      * Normally, this will be the end of a billing month.
+//      */
+//     newOccuredMillis: Long
+// ): (List[ResourceEventModel], List[ResourceEventModel]) = {
+//
+//   val buffer = mutable.ListBuffer[(ResourceEventModel, ResourceEventModel)]()
+//   val checkSet = mutable.Set[ResourceEventModel]()
+//
+//   def doItFor(map: mutable.Map[(String, String), ResourceEventModel]): Unit = {
+//     val resourceEvents = map.valuesIterator
+//     for {
+//       resourceEvent ← resourceEvents
+//       resourceType ← resourceTypesMap.get(resourceEvent.safeResource)
+//       chargingBehavior = chargingBehaviorOfResourceType.apply(resourceType)
+//     } {
+//       if(chargingBehavior.supportsImplicitEvents) {
+//         if(chargingBehavior.mustConstructImplicitEndEventFor(resourceEvent)) {
+//           val implicitEnd = chargingBehavior.constructImplicitEndEventFor(resourceEvent, newOccuredMillis)
+//
+//           if(!checkSet.contains(resourceEvent)) {
+//             checkSet.add(resourceEvent)
+//             buffer append ((resourceEvent, implicitEnd))
+//           }
+//
+//           // remove it anyway
+//           map.remove((resourceEvent.safeResource, resourceEvent.safeInstanceID))
+//         }
+//       }
+//     }
+//   }
+//
+//   doItFor(previousEventOfResourceInstance) // we give priority for previous events
+//   doItFor(implicitlyIssuedStartEventOfResourceInstance) // ... over implicitly issued ones ...
+//
+//   (buffer.view.map(_._1).toList, buffer.view.map(_._2).toList)
+// }
 }
 
 object WorkingUserState {
index e9e932f..f962a70 100644 (file)
@@ -40,7 +40,6 @@ import gr.grnet.aquarium.util.Loggable
 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
 import gr.grnet.aquarium.policy._
 import collection.immutable
-import gr.grnet.aquarium.policy.ResourceType
 import gr.grnet.aquarium.policy.EffectiveUnitPrice
 import gr.grnet.aquarium.charging.Chargeslot
 
@@ -86,18 +85,16 @@ object TimeslotComputations extends Loggable {
       alignedTimeslot: Timeslot,
       policy: PolicyModel,
       agreement: UserAgreementModel,
-      resourceType: ResourceType,
       effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable
   ): SortedMap[Timeslot, Double] = {
 
     // Note that most of the code is taken from calcChangeChunks()
-    val ret = resolveEffectiveUnitPricesForTimeslot(alignedTimeslot, policy, agreement, resourceType, effectivePriceTableSelector)
+    val ret = resolveEffectiveUnitPricesForTimeslot(alignedTimeslot, policy, agreement, effectivePriceTableSelector)
     ret map {case (t,p) => (t,p.unitPrice)}
   }
 
   def computeInitialChargeslots(
       referenceTimeslot: Timeslot,
-      resourceType: ResourceType,
       policyByTimeslot: SortedMap[Timeslot, PolicyModel],
       agreementByTimeslot: SortedMap[Timeslot, UserAgreementModel],
       effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable
@@ -134,7 +131,6 @@ object TimeslotComputations extends Loggable {
         alignedTimeslot,
         policy,
         userAgreement,
-        resourceType,
         effectivePriceTableSelector
       )
 
@@ -210,7 +206,6 @@ object TimeslotComputations extends Loggable {
         alignedTimeslot: Timeslot,
         policy: PolicyModel,
         agreement: UserAgreementModel,
-        resourceType: ResourceType,
         effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable
     ): PriceMap = {
 
index 59991ae..5aa5583 100644 (file)
@@ -41,8 +41,7 @@ import gr.grnet.aquarium.converter.JsonTextFormat
 import gr.grnet.aquarium.store.{IMEventStore, LocalFSEventStore}
 import gr.grnet.aquarium.event.model.im.{StdIMEvent, IMEventModel}
 import gr.grnet.aquarium.actor.message.event.ProcessIMEvent
-import gr.grnet.aquarium.util.date.MutableDateCalc
-import gr.grnet.aquarium.util.{LogHelpers, Tags, shortClassNameOf}
+import gr.grnet.aquarium.util.{LogHelpers, Tags}
 
 /**
  * A [[gr.grnet.aquarium.connector.handler.PayloadHandler]] for
index f13bf97..e1897fb 100644 (file)
@@ -40,14 +40,19 @@ import scala.annotation.tailrec
 import gr.grnet.aquarium.util.shortNameOfType
 
 /**
- * A full price table provides detailed pricing information for all resources.
+ * A full price table provides detailed pricing information for all resource types.
  *
- * @author Christos KK Loverdos <loverdos@gmail.com>
+ * @param perResource The key is some [[gr.grnet.aquarium.policy.ResourceType]]`.name`.
+ *                    The value is a Map from selector to either an [[gr.grnet.aquarium.policy.EffectivePriceTable]]
+ *                    or another Map (that designates another level of search path).
+ *                    See `policy.json` for samples.
+ *
+ *@author Christos KK Loverdos <loverdos@gmail.com>
  */
 
 case class FullPriceTable(
-    perResource: Map[String/*Resource*/,
-                     Map[String/*Per-ChargingBehavior Key, "default" is the default*/, Any]]
+    perResource: Map[String /* The key is some ResourceType.name */,
+                     Map[String /* Use "default" for the simple cases */, Any]]
 ) {
 
   def effectivePriceTableOfSelectorForResource(
@@ -55,36 +60,109 @@ case class FullPriceTable(
       resource: String
   ): EffectivePriceTable = {
 
+    // Most of the code is for exceptional cases, which we identify in detail.
     @tailrec
     def find(
-        selectorPath: List[String],
-        selectorData: Any
+        partialSelectorPath: List[String],
+        partialSelectorData: Any
     ): EffectivePriceTable = {
 
-      selectorPath match {
-        case Nil ⇒
-          // End of selector path. This means that the data must be an EffectivePriceTable
-          selectorData match {
-            case ept: EffectivePriceTable ⇒
-              ept
-
-            case _ ⇒
-              // TODO more informative error message (include selector path, resource?)
-              throw new AquariumInternalError("Got %s instead of an %s", selectorData, shortNameOfType[EffectivePriceTable])
-          }
+      partialSelectorPath match {
+        case selector :: Nil ⇒
+          // One selector, meaning that the selectorData must be a Map[String, EffectivePriceTable]
+          partialSelectorData match {
+            case selectorMap: Map[_,_] ⇒
+              // The selectorData is a map indeed
+              selectorMap.asInstanceOf[Map[String, _]].get(selector) match {
+                case Some(selected: EffectivePriceTable) ⇒
+                  // Yes, it is a map of the right type (OK, we assume keys are always Strings)
+                  // (we only check the value type)
+                  selected
+
+                case Some(badSelected) ⇒
+                  // The selectorData is a map but the value is not of the required type
+                  throw new AquariumInternalError(
+                    "[AQU-SEL-001] Cannot select path %s for resource %s. Found %s instead of an %s at partial selector path %s".format(
+                      selectorPath.mkString("/"),
+                      resource,
+                      badSelected,
+                      shortNameOfType[EffectivePriceTable],
+                      partialSelectorPath.mkString("/")
+                    )
+                  )
 
-        case key :: tailSelectorPath ⇒
-          // Intermediate path. This means we have another round of Map[String, Any]
-          selectorData match {
-            case selectorMap: Map[_, _] ⇒
-              selectorMap.asInstanceOf[Map[String, _]].get(key) match {
                 case None ⇒
-                  throw new AquariumInternalError("Did not find value for selector %s", key)
+                  // The selectorData is a map but it does nto contain the selector
+                  throw new AquariumInternalError(
+                    "[AQU-SEL-002] Cannot select path %s for resource %s. Nothing found at partial selector path %s".format(
+                      selectorPath.mkString("/"),
+                      resource,
+                      partialSelectorPath.mkString("/")
+                    )
+                  )
+              }
+
+
+            case badData ⇒
+              // The selectorData is not a map. So we have just one final selector but no map to select from.
+              throw new AquariumInternalError(
+                "[AQU-SEL-003] Cannot select path %s for resource %s. Found %s instead of a Map at partial selector path %s".format(
+                  selectorPath.mkString("/"),
+                  resource,
+                  badData,
+                  partialSelectorPath.mkString("/")
+                )
+              )
+          }
 
-                case Some(nextSelectorData) ⇒
-                  find(tailSelectorPath, nextSelectorData)
+        case selector :: selectorTail ⇒
+          // More than one selector in the path, meaning that the selectorData must be a Map[String, Map[String, _]]
+          partialSelectorData match {
+            case selectorMap: Map[_,_] ⇒
+             // The selectorData is a map indeed
+              selectorMap.asInstanceOf[Map[String,_]].get(selector) match {
+                case Some(furtherSelectorMap: Map[_,_]) ⇒
+                  // The selectorData is a map and we found the respective value for the selector to be a map.
+                  find(selectorTail, furtherSelectorMap)
+
+                case Some(furtherBad) ⇒
+                  // The selectorData is a map but the respective value is not a map, so that
+                  // the selectorTail path cannot be used.
+                  throw new AquariumInternalError(
+                    "[AQU-SEL-004] Cannot select path %s for resource %s. Found %s instead of a Map at partial selector path %s".format(
+                      selectorPath.mkString("/"),
+                      resource,
+                      furtherBad,
+                      partialSelectorPath.mkString("/")
+                     )
+                  )
+
+                case None ⇒
+                  // The selectorData is a map but it does not contain the selector
+                  throw new AquariumInternalError(
+                    "[AQU-SEL-005] Cannot select path %s for resource %s. Nothing found at partial selector path %s".format(
+                      selectorPath.mkString("/"),
+                      resource,
+                      partialSelectorPath.mkString("/")
+                    )
+                  )
               }
+
+            case badData ⇒
+              // The selectorData is not a Map. So we have more than one selectors but no map to select from.
+              throw new AquariumInternalError(
+                "[AQU-SEL-006] Cannot select path %s for resource %s. Found %s instead of a Map at partial selector path %s".format(
+                  selectorPath.mkString("/"),
+                  resource,
+                  badData,
+                  partialSelectorPath.mkString("/")
+                )
+              )
           }
+
+        case Nil ⇒
+          throw new AquariumInternalError("")
+
       }
     }
 
index 41a5ca8..b3fb84f 100644 (file)
@@ -129,10 +129,7 @@ extends StoreProvider
         model.billingYear,
         model.billingMonth,
         model.chargingReason,
-        model.previousResourceEvents,
-        model.implicitlyIssuedStartEvents,
-        model.accumulatingAmountOfResourceInstance,
-        model.chargingDataOfResourceInstance,
+        model.stateOfResources,
         model.billingPeriodOutOfSyncResourceEventsCounter,
         model.agreementHistory,
         model.walletEntries
index a0ee96b..3ba34bb 100644 (file)
@@ -35,9 +35,8 @@
 
 package gr.grnet.aquarium.store.mongodb
 
-import gr.grnet.aquarium.charging.state.{UserStateModelSkeleton, AgreementHistory, UserStateModel}
+import gr.grnet.aquarium.charging.state.{ResourcesChargingState, UserStateModelSkeleton, AgreementHistory, UserStateModel}
 import gr.grnet.aquarium.charging.reason.ChargingReason
-import gr.grnet.aquarium.event.model.resource.ResourceEventModel
 import gr.grnet.aquarium.charging.wallet.WalletEntry
 import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters}
 
@@ -57,10 +56,7 @@ case class MongoDBUserState(
     billingYear: Int,
     billingMonth: Int,
     chargingReason: ChargingReason,
-    previousResourceEvents: List[ResourceEventModel],
-    implicitlyIssuedStartEvents: List[ResourceEventModel],
-    accumulatingAmountOfResourceInstance: Map[String, Double],
-    chargingDataOfResourceInstance: Map[String, Map[String, Any]],
+    stateOfResources: Map[String, ResourcesChargingState],
     billingPeriodOutOfSyncResourceEventsCounter: Long,
     agreementHistory: AgreementHistory,
     walletEntries: List[WalletEntry]
@@ -90,10 +86,7 @@ object MongoDBUserState {
       model.billingYear,
       model.billingMonth,
       model.chargingReason,
-      model.previousResourceEvents,
-      model.implicitlyIssuedStartEvents,
-      model.accumulatingAmountOfResourceInstance,
-      model.chargingDataOfResourceInstance,
+      model.stateOfResources,
       model.billingPeriodOutOfSyncResourceEventsCounter,
       model.agreementHistory,
       model.walletEntries