Merge branch 'snapshots'
[aquarium] / src / main / scala / gr / grnet / aquarium / computation / TimeslotComputations.scala
index 1a0cd22..1601c14 100644 (file)
 package gr.grnet.aquarium.computation
 
 import collection.immutable.SortedMap
-import com.ckkloverdos.maybe.{NoVal, Maybe, Just}
-import gr.grnet.aquarium.util.{ContextualLogger, Loggable}
-import gr.grnet.aquarium.store.PolicyStore
-import gr.grnet.aquarium.util.date.MutableDateCalc
-import gr.grnet.aquarium.{AquariumInternalError, AquariumException}
-import gr.grnet.aquarium.event.model.resource.ResourceEventModel
-import gr.grnet.aquarium.logic.accounting.algorithm.SimpleExecutableChargingBehaviorAlgorithm
-import gr.grnet.aquarium.logic.accounting.dsl.{Timeslot}
+import gr.grnet.aquarium.util.Loggable
+import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
 import gr.grnet.aquarium.policy._
 import collection.immutable
-import com.ckkloverdos.maybe.Just
-import gr.grnet.aquarium.computation.Chargeslot
-import scala.Some
-import com.ckkloverdos.maybe.Just
-import gr.grnet.aquarium.computation.Chargeslot
-import scala.Some
-import com.ckkloverdos.maybe.Just
-import gr.grnet.aquarium.policy.ResourceType
-import gr.grnet.aquarium.policy.EffectiveUnitPrice
-import gr.grnet.aquarium.computation.Chargeslot
-import scala.Some
+import gr.grnet.aquarium.policy.EffectiveUnitPriceModel
+import gr.grnet.aquarium.message.avro.gen.{EffectiveUnitPriceMsg, PolicyMsg, UserAgreementMsg, FullPriceTableMsg, EffectivePriceTableMsg, ChargeslotMsg}
 
 /**
  * Methods for converting accounting events to wallet entries.
@@ -64,9 +49,7 @@ import scala.Some
  * @author Georgios Gousios <gousiosg@gmail.com>
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
-trait TimeslotComputations extends Loggable {
-  // TODO: favour composition over inheritance until we decide what to do with DSLUtils (and TimeslotComputations).
-  //protected val dslUtils = new DSLUtils {}
+object TimeslotComputations extends Loggable {
 
   /**
    * Breaks a reference timeslot (e.g. billing period) according to policies and agreements.
@@ -76,12 +59,10 @@ trait TimeslotComputations extends Loggable {
    * @param agreementTimeslots
    * @return
    */
-  protected
-  def splitTimeslotByPoliciesAndAgreements(
+  private[this] def splitTimeslotByPoliciesAndAgreements(
       referenceTimeslot: Timeslot,
       policyTimeslots: List[Timeslot],
-      agreementTimeslots: List[Timeslot],
-      clogM: Maybe[ContextualLogger] = NoVal
+      agreementTimeslots: List[Timeslot]
   ): List[Timeslot] = {
 
     // Align policy and agreement validity timeslots to the referenceTimeslot
@@ -98,32 +79,31 @@ trait TimeslotComputations extends Loggable {
    * algorithm and price unit is in effect.
    *
    */
-  protected
-  def resolveEffectiveUnitPrices(
+  private[this] def resolveEffectiveUnitPrices(
       alignedTimeslot: Timeslot,
       policy: PolicyModel,
       agreement: UserAgreementModel,
-      resourceType: ResourceType,
-      clogOpt: Option[ContextualLogger] = None
-  ): SortedMap[Timeslot, Double] = {
-
-    val clog = ContextualLogger.fromOther(clogOpt, logger, "resolveEffectiveUnitPrices()")
+      fullPriceTableModelGetter: (UserAgreementModel, PolicyModel) ⇒ FullPriceTableModel,
+      effectivePriceTableModelSelector: FullPriceTableModel ⇒ EffectivePriceTableModel
+  ): SortedMap[Timeslot, String] = {
 
     // Note that most of the code is taken from calcChangeChunks()
-    val ret = resolveEffectiveUnitPricesForTimeslot(alignedTimeslot, policy, agreement, resourceType)
+    val ret = resolveEffectiveUnitPricesForTimeslot(
+      alignedTimeslot,
+      policy,
+      agreement,
+      fullPriceTableModelGetter,
+      effectivePriceTableModelSelector)
     ret map {case (t,p) => (t,p.unitPrice)}
   }
 
-  protected
   def computeInitialChargeslots(
       referenceTimeslot: Timeslot,
-      resourceType: ResourceType,
       policyByTimeslot: SortedMap[Timeslot, PolicyModel],
       agreementByTimeslot: SortedMap[Timeslot, UserAgreementModel],
-      clogOpt: Option[ContextualLogger] = None
-  ): List[Chargeslot] = {
-
-    val clog = ContextualLogger.fromOther(clogOpt, logger, "computeInitialChargeslots()")
+      fullPriceTableModelGetter: (UserAgreementModel, PolicyModel) ⇒ FullPriceTableModel,
+      effectivePriceTableModelSelector: FullPriceTableModel ⇒ EffectivePriceTableModel
+  ): List[ChargeslotMsg] = {
 
     val policyTimeslots = policyByTimeslot.keySet
     val agreementTimeslots = agreementByTimeslot.keySet
@@ -136,7 +116,8 @@ trait TimeslotComputations extends Loggable {
     }
 
     // 1. Round ONE: split time according to overlapping policies and agreements.
-    val alignedTimeslots = splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList, Just(clog))
+    //val alignedTimeslots = List(referenceTimeslot) //splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList, Just(clog))
+    val alignedTimeslots = splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList)
 
     // 2. Round TWO: Use the aligned timeslots of Round ONE to produce even more
     //    fine-grained timeslots according to applicable algorithms.
@@ -145,24 +126,31 @@ trait TimeslotComputations extends Loggable {
     val allChargeslots = for {
       alignedTimeslot <- alignedTimeslots
     } yield {
+      //val policy = policyByTimeslot.valuesIterator.next()//getPolicyWithin(alignedTimeslot)
       val policy = getPolicyWithin(alignedTimeslot)
       //      clog.debug("dslPolicy = %s", dslPolicy)
+      //val userAgreement = agreementByTimeslot.valuesIterator.next()//getAgreementWithin(alignedTimeslot)
       val userAgreement = getAgreementWithin(alignedTimeslot)
 
-      // TODO: Factor this out, just like we did with:
-      // TODO:  val alignedTimeslots = splitTimeslotByPoliciesAndAgreements
-      // Note that most of the code is already taken from calcChangeChunks()
-      val unitPriceByTimeslot = resolveEffectiveUnitPrices(alignedTimeslot, policy, userAgreement, resourceType, Some(clog))
+      val unitPriceByTimeslot = resolveEffectiveUnitPrices(
+        alignedTimeslot,
+        policy,
+        userAgreement,
+        fullPriceTableModelGetter,
+        effectivePriceTableModelSelector
+      )
 
       // Now, the timeslots must be the same
       val finegrainedTimeslots = unitPriceByTimeslot.keySet
 
-      val chargeslots = for (finegrainedTimeslot ← finegrainedTimeslots) yield {
-        Chargeslot(
-          finegrainedTimeslot.from.getTime,
-          finegrainedTimeslot.to.getTime,
-          unitPriceByTimeslot(finegrainedTimeslot)
-        )
+      val chargeslots = for(finegrainedTimeslot ← finegrainedTimeslots) yield {
+        val cs = new ChargeslotMsg
+
+        cs.setStartMillis(finegrainedTimeslot.from.getTime)
+        cs.setStopMillis(finegrainedTimeslot.to.getTime)
+        cs.setUnitPrice(unitPriceByTimeslot(finegrainedTimeslot))
+
+        cs
       }
 
       chargeslots.toList
@@ -174,105 +162,6 @@ trait TimeslotComputations extends Loggable {
   }
 
   /**
-   * Compute the charge slots generated by a particular resource event.
-   *
-   */
-  def computeFullChargeslots(
-      previousResourceEventOpt: Option[ResourceEventModel],
-      currentResourceEvent: ResourceEventModel,
-      oldCredits: Double,
-      oldTotalAmount: Double,
-      newTotalAmount: Double,
-      resourceType: ResourceType,
-      agreementByTimeslot: SortedMap[Timeslot, UserAgreementModel],
-      policyStore: PolicyStore,
-      clogOpt: Option[ContextualLogger] = None
-  ): (Timeslot, List[Chargeslot]) = {
-
-    val clog = ContextualLogger.fromOther(clogOpt, logger, "computeFullChargeslots()")
-    //    clog.begin()
-
-    val occurredDate = currentResourceEvent.occurredDate
-    val occurredMillis = currentResourceEvent.occurredMillis
-    val chargingBehavior = resourceType.chargingBehavior
-
-    val (referenceTimeslot, policyByTimeslot, previousValue) = chargingBehavior.needsPreviousEventForCreditAndAmountCalculation match {
-      // We need a previous event
-      case true ⇒
-        previousResourceEventOpt match {
-          // We have a previous event
-          case Some(previousResourceEvent) ⇒
-            val referenceTimeslot = Timeslot(previousResourceEvent.occurredDate, occurredDate)
-            // all policies within the interval from previous to current resource event
-            //            clog.debug("Calling policyStore.loadAndSortPoliciesWithin(%s)", referenceTimeslot)
-            // TODO: store policies in mem?
-            val policyByTimeslot = policyStore.loadAndSortPoliciesWithin(referenceTimeslot.from.getTime, referenceTimeslot.to.getTime)
-
-            (referenceTimeslot, policyByTimeslot, previousResourceEvent.value)
-
-          // We do not have a previous event
-          case None ⇒
-            throw new AquariumInternalError(
-              "Unable to charge. No previous event given for %s".format(currentResourceEvent.toDebugString))
-        }
-
-      // We do not need a previous event
-      case false ⇒
-        // ... so we cannot compute timedelta from a previous event, there is just one chargeslot
-        // referring to (almost) an instant in time
-        val previousValue = chargingBehavior.getResourceInstanceUndefinedAmount
-
-        // TODO: Check semantics of this
-        val referenceTimeslot = Timeslot(new MutableDateCalc(occurredDate).goPreviousMilli.toDate, occurredDate)
-
-        // TODO: store policies in mem?
-        val relevantPolicyOpt: Option[PolicyModel] = policyStore.loadValidPolicyAt(occurredMillis)
-
-        val policyByTimeslot = relevantPolicyOpt match {
-          case Some(relevantPolicy) ⇒
-            SortedMap(referenceTimeslot -> relevantPolicy)
-
-          case None ⇒
-            throw new AquariumInternalError("No relevant policy found for %s".format(referenceTimeslot))
-        }
-
-        (referenceTimeslot, policyByTimeslot, previousValue)
-    }
-
-    val initialChargeslots = computeInitialChargeslots(
-      referenceTimeslot,
-      resourceType,
-      policyByTimeslot,
-      agreementByTimeslot,
-      Some(clog)
-    )
-
-    val fullChargeslots = initialChargeslots.map {
-      case chargeslot@Chargeslot(startMillis, stopMillis, unitPrice, _) ⇒
-        val valueMap = chargingBehavior.makeValueMap(
-          oldCredits,
-          oldTotalAmount,
-          newTotalAmount,
-          stopMillis - startMillis,
-          previousValue,
-          currentResourceEvent.value,
-          unitPrice
-        )
-
-        //              clog.debug("execAlgorithm = %s", execAlgorithm)
-        clog.debugMap("valueMap", valueMap, 1)
-
-        // This is it
-        val credits = SimpleExecutableChargingBehaviorAlgorithm.apply(valueMap)
-        chargeslot.copyWithCredits(credits)
-    }
-
-    val result = referenceTimeslot -> fullChargeslots
-
-    result
-  }
-
-  /**
    * Given two lists of timeslots, produce a list which contains the
    * set of timeslot slices, as those are defined by
    * timeslot overlaps.
@@ -288,8 +177,10 @@ trait TimeslotComputations extends Loggable {
    *
    * result: List(Timeslot(a.from, b.to), Timeslot(b.to, a.to))
    */
-  private[computation] def alignTimeslots(a: List[Timeslot],
-                                    b: List[Timeslot]): List[Timeslot] = {
+  private[this] def alignTimeslots(
+      a: List[Timeslot],
+      b: List[Timeslot]
+  ): List[Timeslot] = {
 
     def safeTail(foo: List[Timeslot]) = foo match {
       case Nil => List()
@@ -299,7 +190,7 @@ trait TimeslotComputations extends Loggable {
 
     if(a.isEmpty) return b
     if(b.isEmpty) return a
-
+    if(a.head.from != b.head.from)
     assert(a.head.from == b.head.from)
 
     if(a.head.endsAfter(b.head)) {
@@ -313,69 +204,51 @@ trait TimeslotComputations extends Loggable {
     }
   }
 
-    type PriceMap =  immutable.SortedMap[Timeslot, EffectiveUnitPrice]
-    private type PriceList = List[EffectiveUnitPrice]
-    private def emptyMap = immutable.SortedMap[Timeslot,EffectiveUnitPrice]()
+    type PriceMap =  immutable.SortedMap[Timeslot, EffectiveUnitPriceModel]
+    private type PriceList = List[EffectiveUnitPriceModel]
+    private def emptyMap = immutable.SortedMap[Timeslot,EffectiveUnitPriceModel]()
 
     /**
      * Resolves the effective price list for each chunk of the
      * provided timeslot and returns it as a Map
      */
-    private def resolveEffectiveUnitPricesForTimeslot(
-                                               alignedTimeslot: Timeslot,
-                                               policy: PolicyModel,
-                                               agreement: UserAgreementModel,
-                                               resourceType: ResourceType
-                                               ): PriceMap = {
-
-      val role = agreement.role
-      val fullPriceTable = agreement.fullPriceTableRef match {
-        case PolicyDefinedFullPriceTableRef ⇒
-          policy.roleMapping.get(role) match {
-            case Some(fullPriceTable) ⇒
-              fullPriceTable
-
-            case None ⇒
-              throw new AquariumInternalError("Unknown role %s".format(role))
-          }
-
-        case AdHocFullPriceTableRef(fullPriceTable) ⇒
-          fullPriceTable
-      }
+    private[this] def resolveEffectiveUnitPricesForTimeslot(
+        alignedTimeslot: Timeslot,
+        policy: PolicyModel,
+        userAgreement: UserAgreementModel,
+        fullPriceTableModelGetter: (UserAgreementModel, PolicyModel) ⇒ FullPriceTableModel,
+        effectivePriceTableModelSelector: FullPriceTableModel ⇒ EffectivePriceTableModel
+    ): PriceMap = {
 
-      val effectivePriceTable = fullPriceTable.perResource.get(resourceType.name) match {
-        case None ⇒
-          throw new AquariumInternalError("Unknown resource type %s".format(role))
-
-        case Some(effectivePriceTable) ⇒
-          effectivePriceTable
-      }
+      val fullPriceTable = fullPriceTableModelGetter(userAgreement, policy)
+      val effectivePriceTable = effectivePriceTableModelSelector(fullPriceTable)
 
       resolveEffective(alignedTimeslot, effectivePriceTable.priceOverrides)
+      //immutable.SortedMap(alignedTimeslot -> effectivePriceTable.priceOverrides.head)
     }
 
-    private def printPriceList(p: PriceList) : Unit = {
+  private[this] def printPriceList(p: PriceList) : Unit = {
       Console.err.println("BEGIN PRICE LIST")
       for { p1 <- p } Console.err.println(p1)
       Console.err.println("END PRICE LIST")
     }
 
-    private def printPriceMap(m: PriceMap) = {
+  private[this] def printPriceMap(m: PriceMap) = {
       Console.err.println("BEGIN PRICE MAP")
       for { (t,p) <- m.toList } Console.err.println("Timeslot " + t + "\t\t" + p)
       Console.err.println("END PRICE MAP")
     }
 
-    private def resolveEffective(alignedTimeslot: Timeslot,p:PriceList): PriceMap = {
-      Console.err.println("\n\nInput timeslot: " + alignedTimeslot + "\n\n")
-      printPriceList(p)
+  private[this] def resolveEffective(alignedTimeslot: Timeslot,p:PriceList): PriceMap = {
+      //Console.err.println("\n\nInput timeslot: " + alignedTimeslot + "\n\n")
+      //printPriceList(p)
       val ret =  resolveEffective3(alignedTimeslot,p) //HERE
-      printPriceMap(ret)
+      //printPriceMap(ret)
       ret
     }
 
 
-    private def resolveEffective3(alignedTimeslot: Timeslot, effectiveUnitPrices: PriceList): PriceMap =
+  private[this] def resolveEffective3(alignedTimeslot: Timeslot, effectiveUnitPrices: PriceList): PriceMap =
       effectiveUnitPrices match {
         case Nil =>
           emptyMap