Refactor charging computation + data
authorChristos KK Loverdos <loverdos@gmail.com>
Mon, 7 May 2012 15:21:58 +0000 (18:21 +0300)
committerChristos KK Loverdos <loverdos@gmail.com>
Mon, 7 May 2012 15:21:58 +0000 (18:21 +0300)
29 files changed:
src/main/scala/gr/grnet/aquarium/actor/message/GetUserStateResponse.scala
src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala
src/main/scala/gr/grnet/aquarium/computation/BillingMonthInfo.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/DefaultUserStateComputations.scala [moved from src/main/scala/gr/grnet/aquarium/user/DefaultUserStateComputations.scala with 93% similarity]
src/main/scala/gr/grnet/aquarium/computation/UserState.scala [moved from src/main/scala/gr/grnet/aquarium/user/UserState.scala with 60% similarity]
src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala [moved from src/main/scala/gr/grnet/aquarium/user/UserStateComputations.scala with 64% similarity]
src/main/scala/gr/grnet/aquarium/computation/UserStateWorker.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/data/AgreementSnapshot.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/data/AgreementsSnapshot.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/data/CreditSnapshot.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/data/IMStateSnapshot.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsSnapshot.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsWorker.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsSnapshot.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsWorker.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsSnapshot.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsWorker.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesMap.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesSnapshot.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/data/ResourceInstanceSnapshot.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReason.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReasonCodes.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/logic/accounting/Accounting.scala
src/main/scala/gr/grnet/aquarium/store/UserStateStore.scala
src/main/scala/gr/grnet/aquarium/store/memory/MemStore.scala
src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStore.scala
src/main/scala/gr/grnet/aquarium/user/UserDataSnapshot.scala [deleted file]
src/main/scala/gr/grnet/aquarium/util/ContextualLogger.scala
src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala

index 273b477..b7158fd 100644 (file)
@@ -35,7 +35,7 @@
 
 package gr.grnet.aquarium.actor.message
 
-import gr.grnet.aquarium.user.UserState
+import gr.grnet.aquarium.computation.UserState
 
 /**
  *
index 5b73185..96c4d61 100644 (file)
@@ -38,7 +38,6 @@ package service
 package user
 
 import gr.grnet.aquarium.actor._
-import gr.grnet.aquarium.user._
 
 import gr.grnet.aquarium.util.shortClassNameOf
 import message.config.{ActorProviderConfigured, AquariumPropertiesLoaded}
@@ -48,6 +47,8 @@ import gr.grnet.aquarium.Configurator
 import gr.grnet.aquarium.util.date.{TimeHelpers, MutableDateCalc}
 import gr.grnet.aquarium.actor.message.event.{ProcessResourceEvent, ProcessIMEvent}
 import gr.grnet.aquarium.actor.message.{GetUserStateResponse, GetUserBalanceResponse, GetUserStateRequest, GetUserBalanceRequest}
+import gr.grnet.aquarium.computation.data.IMStateSnapshot
+import gr.grnet.aquarium.computation.UserState
 
 /**
  *
@@ -123,7 +124,7 @@ class UserActor extends ReflectiveRoleableActor {
       }
     }
 
-    this._imState = IMStateSnapshot(imEvent, now)
+    this._imState = IMStateSnapshot(imEvent)
     DEBUG("%s %s", if(hadIMState) "Update" else "Set", shortClassNameOf(this._imState))
   }
 
diff --git a/src/main/scala/gr/grnet/aquarium/computation/BillingMonthInfo.scala b/src/main/scala/gr/grnet/aquarium/computation/BillingMonthInfo.scala
new file mode 100644 (file)
index 0000000..8e23a79
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * 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.computation
+
+import gr.grnet.aquarium.util.date.MutableDateCalc
+
+/**
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+final class BillingMonthInfo private(val year: Int,
+                                     val month: Int,
+                                     val startMillis: Long,
+                                     val stopMillis: Long) extends Ordered[BillingMonthInfo] {
+
+  def previousMonth: BillingMonthInfo = {
+    BillingMonthInfo.fromDateCalc(new MutableDateCalc(year, month).goPreviousMonth)
+  }
+
+  def nextMonth: BillingMonthInfo = {
+    BillingMonthInfo.fromDateCalc(new MutableDateCalc(year, month).goNextMonth)
+  }
+
+
+  def compare(that: BillingMonthInfo) = {
+    val ds = this.startMillis - that.startMillis
+    if(ds < 0) -1 else if(ds == 0) 0 else 1
+  }
+
+
+  override def equals(any: Any) = any match {
+    case that: BillingMonthInfo ⇒
+      this.year == that.year && this.month == that.month // normally everything else MUST be the same by construction
+    case _ ⇒
+      false
+  }
+
+  override def hashCode() = {
+    31 * year + month
+  }
+
+  override def toString = "%s-%02d".format(year, month)
+}
+
+object BillingMonthInfo {
+  def fromMillis(millis: Long): BillingMonthInfo = {
+    fromDateCalc(new MutableDateCalc(millis))
+  }
+
+  def fromDateCalc(mdc: MutableDateCalc): BillingMonthInfo = {
+    val year = mdc.getYear
+    val month = mdc.getMonthOfYear
+    val startMillis = mdc.goStartOfThisMonth.getMillis
+    val stopMillis = mdc.goEndOfThisMonth.getMillis // no need to `copy` here, since we are discarding `mdc`
+
+    new BillingMonthInfo(year, month, startMillis, stopMillis)
+  }
+}
  * or implied, of GRNET S.A.
  */
 
-package gr.grnet.aquarium.user
+package gr.grnet.aquarium.computation
 
 /**
- * Default implementation for [[gr.grnet.aquarium.user.UserStateComputations]].
+ * Default implementation for [[gr.grnet.aquarium.computation.UserStateComputations]].
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
  * or implied, of GRNET S.A.
  */
 
-package gr.grnet.aquarium.user
+package gr.grnet.aquarium.computation
 
-import gr.grnet.aquarium.util.json.JsonSupport
-import gr.grnet.aquarium.logic.accounting.dsl.DSLAgreement
-import com.ckkloverdos.maybe.{Failed, Maybe}
-import gr.grnet.aquarium.util.date.MutableDateCalc
-import gr.grnet.aquarium.event.{NewWalletEntry, WalletEntry}
 import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters}
-import gr.grnet.aquarium.AquariumException
-import gr.grnet.aquarium.event.im.IMEventModel
+import gr.grnet.aquarium.event.{NewWalletEntry, WalletEntry}
 import org.bson.types.ObjectId
-
+import gr.grnet.aquarium.util.json.JsonSupport
+import gr.grnet.aquarium.logic.accounting.dsl.DSLAgreement
+import com.ckkloverdos.maybe.Maybe
+import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, UserStateChangeReason, InitialUserStateSetup, IMEventArrival}
+import gr.grnet.aquarium.AquariumInternalError
+import gr.grnet.aquarium.computation.data.{AgreementSnapshot, ResourceInstanceSnapshot, OwnedResourcesSnapshot, AgreementsSnapshot, CreditSnapshot, LatestResourceEventsSnapshot, ImplicitlyIssuedResourceEventsSnapshot, IMStateSnapshot}
+import gr.grnet.aquarium.event.im.{StdIMEvent, IMEventModel}
 
 /**
  * A comprehensive representation of the User's state.
@@ -82,7 +82,7 @@ import org.bson.types.ObjectId
  *          The wallet entries computed. Not all user states need to holds wallet entries,
  *          only those that refer to billing periods (end of billing period).
   * @param lastChangeReason
- *          The [[gr.grnet.aquarium.user.UserStateChangeReason]] for which the usr state has changed.
+ *          The [[gr.grnet.aquarium.computation.reason.UserStateChangeReason]] for which the usr state has changed.
  * @param totalEventsProcessedCounter
  * @param parentUserStateId
  *          The `ID` of the parent state. The parent state is the one used as a reference point in order to calculate
@@ -157,9 +157,10 @@ case class UserState(
     billingPeriodOutOfSyncResourceEventsCounter: Long,
     imStateSnapshot: IMStateSnapshot,
     creditsSnapshot: CreditSnapshot,
-    agreementsSnapshot: AgreementSnapshot,
+    agreementsSnapshot: AgreementsSnapshot,
     ownedResourcesSnapshot: OwnedResourcesSnapshot,
     newWalletEntries: List[NewWalletEntry],
+    occurredMillis: Long, // The time fro which this state is relevant
     // The last known change reason for this userState
     lastChangeReason: UserStateChangeReason = NoSpecificChangeReason,
     totalEventsProcessedCounter: Long = 0L,
@@ -169,20 +170,6 @@ case class UserState(
     _id: ObjectId = new ObjectId()
 ) extends JsonSupport {
 
-  private[this] def _allSnapshots: List[Long] = {
-    List(
-      imStateSnapshot.snapshotTime,
-      creditsSnapshot.snapshotTime, agreementsSnapshot.snapshotTime,
-      ownedResourcesSnapshot.snapshotTime,
-      implicitlyIssuedSnapshot.snapshotTime,
-      latestResourceEventsSnapshot.snapshotTime
-    )
-  }
-
-  def oldestSnapshotTime: Long = _allSnapshots min
-
-  def newestSnapshotTime: Long  = _allSnapshots max
-
   def idOpt: Option[String] = _id match {
     case null ⇒ None
     case _id  ⇒ Some(_id.toString)
@@ -192,16 +179,11 @@ case class UserState(
 //
 //  def userCreationFormatedDate = new MutableDateCalc(userCreationMillis).toString
 
-  def maybeDSLAgreement(at: Long): Maybe[DSLAgreement] = {
-    agreementsSnapshot match {
-      case snapshot @ AgreementSnapshot(data, _) ⇒
-        snapshot.getAgreement(at)
-      case _ ⇒
-       Failed(new AquariumException("No agreement snapshot found for user %s".format(userID)))
-    }
+  def findDSLAgreementForTime(at: Long): Option[DSLAgreement] = {
+    agreementsSnapshot.findForTime(at)
   }
 
-  def findResourceInstanceSnapshot(resource: String, instanceId: String): Maybe[ResourceInstanceSnapshot] = {
+  def findResourceInstanceSnapshot(resource: String, instanceId: String): Option[ResourceInstanceSnapshot] = {
     ownedResourcesSnapshot.findResourceInstanceSnapshot(resource, instanceId)
   }
 
@@ -220,7 +202,7 @@ case class UserState(
       ownedResourcesSnapshot = newResources,
       stateChangeCounter = this.stateChangeCounter + 1)
   }
-  
+
   def copyForChangeReason(changeReason: UserStateChangeReason) = {
     this.copy(lastChangeReason = changeReason)
   }
@@ -230,8 +212,9 @@ case class UserState(
   def modifyFromIMEvent(imEvent: IMEventModel, snapshotMillis: Long): UserState = {
     this.copy(
       isInitial = false,
-      imStateSnapshot = IMStateSnapshot(imEvent, snapshotMillis),
-      lastChangeReason = IMEventArrival(imEvent)
+      imStateSnapshot = IMStateSnapshot(imEvent),
+      lastChangeReason = IMEventArrival(imEvent),
+      occurredMillis = snapshotMillis
     )
   }
 
@@ -243,7 +226,6 @@ case class UserState(
 //    calculationReason)
 }
 
-
 object UserState {
   def fromJson(json: String): UserState = {
     StdConverters.AllConverters.convertEx[UserState](JsonTextFormat(json))
@@ -253,157 +235,82 @@ object UserState {
     final val _id = "_id"
     final val userID = "userID"
   }
-}
-
-final class BillingMonthInfo private(val year: Int,
-                                     val month: Int,
-                                     val startMillis: Long,
-                                     val stopMillis: Long) extends Ordered[BillingMonthInfo] {
-
-  def previousMonth: BillingMonthInfo = {
-    BillingMonthInfo.fromDateCalc(new MutableDateCalc(year, month).goPreviousMonth)
-  }
-
-  def nextMonth: BillingMonthInfo = {
-    BillingMonthInfo.fromDateCalc(new MutableDateCalc(year, month).goNextMonth)
-  }
 
+  def createInitialUserState(imEvent: IMEventModel, credits: Double, agreementName: String) = {
+    if(!imEvent.isCreateUser) {
+      throw new AquariumInternalError(
+        "Got '%s' instead of '%s'".format(imEvent.eventType, IMEventModel.EventTypeNames.create))
+    }
 
-  def compare(that: BillingMonthInfo) = {
-    val ds = this.startMillis - that.startMillis
-    if(ds < 0) -1 else if(ds == 0) 0 else 1
-  }
-
-
-  override def equals(any: Any) = any match {
-    case that: BillingMonthInfo ⇒
-      this.year == that.year && this.month == that.month // normally everything else MUST be the same by construction
-    case _ ⇒
-      false
-  }
-
-  override def hashCode() = {
-    31 * year + month
+    val userID = imEvent.userID
+    val userCreationMillis = imEvent.occurredMillis
+
+    UserState(
+      true,
+      userID,
+      userCreationMillis,
+      0L,
+      false,
+      null,
+      ImplicitlyIssuedResourceEventsSnapshot(List()),
+      Nil,
+      Nil,
+      LatestResourceEventsSnapshot(List()),
+      0L,
+      0L,
+      IMStateSnapshot(imEvent),
+      CreditSnapshot(credits),
+      AgreementsSnapshot(List(AgreementSnapshot(agreementName, userCreationMillis))),
+      OwnedResourcesSnapshot(Nil),
+      Nil,
+      userCreationMillis,
+      InitialUserStateSetup
+    )
   }
 
-  override def toString = "%s-%02d".format(year, month)
-}
-
-object BillingMonthInfo {
-  def fromMillis(millis: Long): BillingMonthInfo = {
-    fromDateCalc(new MutableDateCalc(millis))
+  def createInitialUserState(userID: String,
+                             userCreationMillis: Long,
+                             isActive: Boolean,
+                             credits: Double,
+                             roleNames: List[String] = List(),
+                             agreementName: String = DSLAgreement.DefaultAgreementName) = {
+    val now = userCreationMillis
+
+    UserState(
+      true,
+      userID,
+      userCreationMillis,
+      0L,
+      false,
+      null,
+      ImplicitlyIssuedResourceEventsSnapshot(List()),
+      Nil,
+      Nil,
+      LatestResourceEventsSnapshot(List()),
+      0L,
+      0L,
+      IMStateSnapshot(
+        StdIMEvent(
+          "",
+          now, now, userID,
+          "",
+          isActive, roleNames.headOption.getOrElse("default"),
+          "1.0",
+          IMEventModel.EventTypeNames.create, Map())
+      ),
+      CreditSnapshot(credits),
+      AgreementsSnapshot(List(AgreementSnapshot(agreementName, userCreationMillis))),
+      OwnedResourcesSnapshot(Nil),
+      Nil,
+      now,
+      InitialUserStateSetup
+    )
   }
 
-  def fromDateCalc(mdc: MutableDateCalc): BillingMonthInfo = {
-    val year = mdc.getYear
-    val month = mdc.getMonthOfYear
-    val startMillis = mdc.goStartOfThisMonth.getMillis
-    val stopMillis  = mdc.goEndOfThisMonth.getMillis // no need to `copy` here, since we are discarding `mdc`
-
-    new BillingMonthInfo(year, month, startMillis, stopMillis)
+  def createInitialUserStateFrom(us: UserState): UserState = {
+    createInitialUserState(
+      us.imStateSnapshot.imEvent,
+      us.creditsSnapshot.creditAmount,
+      us.agreementsSnapshot.agreementsByTimeslot.valuesIterator.toList.last)
   }
 }
-
-sealed trait UserStateChangeReason {
-  /**
-   * Return `true` if the result of the calculation should be stored back to the
-   * [[gr.grnet.aquarium.store.UserStateStore]].
-   *
-   */
-  def shouldStoreUserState: Boolean
-
-  def shouldStoreCalculatedWalletEntries: Boolean
-
-  def forPreviousBillingMonth: UserStateChangeReason
-
-  def calculateCreditsForImplicitlyTerminated: Boolean
-
-  def code: UserStateChangeReasonCodes.ChangeReasonCode
-}
-
-object UserStateChangeReasonCodes {
-  type ChangeReasonCode = Int
-
-  final val InitialSetupCode       = 1
-  final val NoSpecificChangeCode   = 2
-  final val MonthlyBillingCode     = 3
-  final val RealtimeBillingCode    = 4
-  final val IMEventArrivalCode     = 5
-}
-
-case object InitialUserStateSetup extends UserStateChangeReason {
-  def shouldStoreUserState = true
-
-  def shouldStoreCalculatedWalletEntries = false
-
-  def forPreviousBillingMonth = this
-
-  def calculateCreditsForImplicitlyTerminated = false
-
-  def code = UserStateChangeReasonCodes.InitialSetupCode
-}
-/**
- * A calculation made for no specific reason. Can be for testing, for example.
- *
- */
-case object NoSpecificChangeReason extends UserStateChangeReason {
-  def shouldStoreUserState = false
-
-  def shouldStoreCalculatedWalletEntries = false
-
-  def forBillingMonthInfo(bmi: BillingMonthInfo) = this
-
-  def forPreviousBillingMonth = this
-
-  def calculateCreditsForImplicitlyTerminated = false
-
-  def code = UserStateChangeReasonCodes.NoSpecificChangeCode
-}
-
-/**
- * An authoritative calculation for the billing period.
- *
- * This marks a state for caching.
- *
- * @param billingMonthInfo
- */
-case class MonthlyBillingCalculation(billingMonthInfo: BillingMonthInfo) extends UserStateChangeReason {
-  def shouldStoreUserState = true
-
-  def shouldStoreCalculatedWalletEntries = true
-
-  def forPreviousBillingMonth = MonthlyBillingCalculation(billingMonthInfo.previousMonth)
-
-  def calculateCreditsForImplicitlyTerminated = true
-
-  def code = UserStateChangeReasonCodes.MonthlyBillingCode
-}
-
-/**
- * Used for the realtime billing calculation.
- *
- * @param forWhenMillis The time this calculation is for
- */
-case class RealtimeBillingCalculation(forWhenMillis: Long) extends UserStateChangeReason {
-  def shouldStoreUserState = false
-
-  def shouldStoreCalculatedWalletEntries = false
-
-  def forPreviousBillingMonth = this
-
-  def calculateCreditsForImplicitlyTerminated = false
-
-  def code = UserStateChangeReasonCodes.RealtimeBillingCode
-}
-
-case class IMEventArrival(imEvent: IMEventModel) extends UserStateChangeReason {
-  def shouldStoreUserState = true
-
-  def shouldStoreCalculatedWalletEntries = false
-
-  def forPreviousBillingMonth = this
-
-  def calculateCreditsForImplicitlyTerminated = false
-
-  def code = UserStateChangeReasonCodes.IMEventArrivalCode
-}
@@ -33,8 +33,7 @@
  * or implied, of GRNET S.A.
  */
 
-package gr.grnet.aquarium.user
-
+package gr.grnet.aquarium.computation
 
 import scala.collection.mutable
 import gr.grnet.aquarium.util.{ContextualLogger, Loggable}
@@ -47,89 +46,14 @@ import gr.grnet.aquarium.event.NewWalletEntry
 import gr.grnet.aquarium.event.resource.ResourceEventModel
 import gr.grnet.aquarium.event.im.{IMEventModel, StdIMEvent}
 import gr.grnet.aquarium.{AquariumInternalError, AquariumException}
+import gr.grnet.aquarium.computation.data._
+import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, UserStateChangeReason, InitialUserStateSetup}
 
 /**
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 class UserStateComputations extends Loggable {
-  def createInitialUserState(imEvent: IMEventModel, credits: Double, agreementName: String) = {
-    if(!imEvent.isCreateUser) {
-      throw new AquariumInternalError(
-        "Got '%s' instead of '%s'".format(imEvent.eventType, IMEventModel.EventTypeNames.create))
-    }
-
-    val userID = imEvent.userID
-    val userCreationMillis = imEvent.occurredMillis
-    val now = TimeHelpers.nowMillis()
-
-    UserState(
-      true,
-      userID,
-      userCreationMillis,
-      0L,
-      false,
-      null,
-      ImplicitlyIssuedResourceEventsSnapshot(List(), now),
-      Nil,
-      Nil,
-      LatestResourceEventsSnapshot(List(), now),
-      0L,
-      0L,
-      IMStateSnapshot(imEvent, now),
-      CreditSnapshot(credits, now),
-      AgreementSnapshot(List(Agreement(agreementName, userCreationMillis)), now),
-      OwnedResourcesSnapshot(Nil, now),
-      Nil,
-      InitialUserStateSetup
-    )
-  }
-
-  def createInitialUserState(userID: String,
-                             userCreationMillis: Long,
-                             isActive: Boolean,
-                             credits: Double,
-                             roleNames: List[String] = List(),
-                             agreementName: String = DSLAgreement.DefaultAgreementName) = {
-    val now = userCreationMillis
-
-    UserState(
-      true,
-      userID,
-      userCreationMillis,
-      0L,
-      false,
-      null,
-      ImplicitlyIssuedResourceEventsSnapshot(List(), now),
-      Nil,
-      Nil,
-      LatestResourceEventsSnapshot(List(), now),
-      0L,
-      0L,
-      IMStateSnapshot(
-        StdIMEvent(
-          "",
-          now, now, userID,
-          "",
-          isActive, roleNames.headOption.getOrElse("default"),
-          "1.0",
-          IMEventModel.EventTypeNames.create, Map()),
-        now
-      ),
-      CreditSnapshot(credits, now),
-      AgreementSnapshot(List(Agreement(agreementName, userCreationMillis)), now),
-      OwnedResourcesSnapshot(Nil, now),
-      Nil,
-      InitialUserStateSetup
-    )
-  }
-
-  def createInitialUserStateFrom(us: UserState): UserState = {
-    createInitialUserState(
-      us.imStateSnapshot.imEvent,
-      us.creditsSnapshot.creditAmount,
-      us.agreementsSnapshot.agreementsByTimeslot.valuesIterator.toList.last)
-  }
 
   def findUserStateAtEndOfBillingMonth(userId: String,
                                        billingMonthInfo: BillingMonthInfo,
@@ -166,14 +90,14 @@ class UserStateComputations extends Loggable {
     val userCreationMillis = currentUserState.userCreationMillis
     val userCreationDateCalc = new MutableDateCalc(userCreationMillis)
     val billingMonthStartMillis = billingMonthInfo.startMillis
-    val billingMonthStopMillis  = billingMonthInfo.stopMillis
+    val billingMonthStopMillis = billingMonthInfo.stopMillis
 
     if(billingMonthStopMillis < userCreationMillis) {
       // If the user did not exist for this billing month, piece of cake
       clog.debug("User did not exist before %s", userCreationDateCalc)
 
       // NOTE: Reason here will be: InitialUserStateSetup$
-      val initialUserState0 = createInitialUserStateFrom(currentUserState)
+      val initialUserState0 = UserState.createInitialUserStateFrom(currentUserState)
       val initialUserState1 = userStateStore.insertUserState(initialUserState0)
 
       clog.debug("Returning INITIAL state [_id=%s] %s".format(initialUserState1._id, initialUserState1))
@@ -198,33 +122,33 @@ class UserStateComputations extends Loggable {
         case Some(latestUserState) ⇒
           // Found a "latest" user state but need to see if it is indeed the true and one latest.
           // For this reason, we must count the events again.
-         val latestStateOOSEventsCounter = latestUserState.billingPeriodOutOfSyncResourceEventsCounter
-         val actualOOSEventsCounter = resourceEventStore.countOutOfSyncEventsForBillingPeriod(
-           userId,
-           billingMonthStartMillis,
-           billingMonthStopMillis)
-
-         val counterDiff = actualOOSEventsCounter - latestStateOOSEventsCounter
-         counterDiff match {
-           // ZERO, we are OK!
-           case 0 ⇒
-             // NOTE: Keep the caller's calculation reason
-             latestUserState.copyForChangeReason(calculationReason)
-
-           // We had more, so must recompute
-           case n if n > 0 ⇒
-             clog.debug(
-               "Found %s out of sync events (%s more), will have to (re)compute user state", actualOOSEventsCounter, n)
-             val result = doCompute
-             clog.end()
-             result
-
-           // We had less????
-           case n if n < 0 ⇒
-             val errMsg = "Found %s out of sync events (%s less). DB must be inconsistent".format(actualOOSEventsCounter, n)
-             clog.warn(errMsg)
-             throw new AquariumException(errMsg)
-         }
+          val latestStateOOSEventsCounter = latestUserState.billingPeriodOutOfSyncResourceEventsCounter
+          val actualOOSEventsCounter = resourceEventStore.countOutOfSyncEventsForBillingPeriod(
+            userId,
+            billingMonthStartMillis,
+            billingMonthStopMillis)
+
+          val counterDiff = actualOOSEventsCounter - latestStateOOSEventsCounter
+          counterDiff match {
+            // ZERO, we are OK!
+            case 0 ⇒
+              // NOTE: Keep the caller's calculation reason
+              latestUserState.copyForChangeReason(calculationReason)
+
+            // We had more, so must recompute
+            case n if n > 0 ⇒
+              clog.debug(
+                "Found %s out of sync events (%s more), will have to (re)compute user state", actualOOSEventsCounter, n)
+              val result = doCompute
+              clog.end()
+              result
+
+            // We had less????
+            case n if n < 0 ⇒
+              val errMsg = "Found %s out of sync events (%s less). DB must be inconsistent".format(actualOOSEventsCounter, n)
+              clog.warn(errMsg)
+              throw new AquariumException(errMsg)
+          }
       }
     }
   }
@@ -233,6 +157,7 @@ class UserStateComputations extends Loggable {
   def rcDebugInfo(rcEvent: ResourceEventModel) = {
     rcEvent.toDebugString(false)
   }
+
   //- Utility methods
 
   def processResourceEvent(startingUserState: UserState,
@@ -329,7 +254,7 @@ class UserStateComputations extends Loggable {
 
             if(stateChangeReason.shouldStoreCalculatedWalletEntries) {
               val newWalletEntry = NewWalletEntry(
-                userStateWorker.userId,
+                userStateWorker.userID,
                 newCreditsDiff,
                 oldCredits,
                 newCredits,
@@ -353,7 +278,7 @@ class UserStateComputations extends Loggable {
             }
 
             _workingUserState = _workingUserState.copy(
-              creditsSnapshot = CreditSnapshot(newCredits, TimeHelpers.nowMillis()),
+              creditsSnapshot = CreditSnapshot(newCredits),
               stateChangeCounter = _workingUserState.stateChangeCounter + 1,
               totalEventsProcessedCounter = _workingUserState.totalEventsProcessedCounter + 1
             )
@@ -492,7 +417,7 @@ class UserStateComputations extends Loggable {
     // Now, the previous and implicitly started must be our base for the following computation, so we create an
     // appropriate worker
     val specialUserStateWorker = UserStateWorker(
-      userStateWorker.userId,
+      userStateWorker.userID,
       LatestResourceEventsWorker.fromList(specialEvents),
       ImplicitlyIssuedResourceEventsWorker.Empty,
       IgnoredFirstResourceEventsWorker.Empty,
@@ -535,142 +460,3 @@ class UserStateComputations extends Loggable {
     _workingUserState
   }
 }
-
-/**
- * A helper object holding intermediate state/results during resource event processing.
- *
- * @param previousResourceEvents
- *          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.
- *
- * @param implicitlyIssuedStartEvents
- *          The implicitly issued resource events at the beginning of the billing period.
- *
- * @param ignoredFirstResourceEvents
- *          The resource events that were first (and unused) of their kind.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case class UserStateWorker(userId: String,
-                           previousResourceEvents: LatestResourceEventsWorker,
-                           implicitlyIssuedStartEvents: ImplicitlyIssuedResourceEventsWorker,
-                           ignoredFirstResourceEvents: IgnoredFirstResourceEventsWorker,
-                           accounting: Accounting,
-                           resourcesMap: DSLResourcesMap) {
-
-  /**
-   * Finds the previous resource event by checking two possible sources: a) The implicitly terminated resource
-   * events and b) the explicit previous resource events. If the event is found, it is removed from the
-   * respective source.
-   *
-   * If the event is not found, then this must be for a new resource instance.
-   * (and probably then some `zero` resource event must be implied as the previous one)
-   *
-   * @param resource
-   * @param instanceId
-   * @return
-   */
-  def findAndRemovePreviousResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = {
-    // implicitly issued events are checked first
-    implicitlyIssuedStartEvents.findAndRemoveResourceEvent(resource, instanceId) match {
-      case some @ Some(_) ⇒
-        some
-      case None ⇒
-        // explicit previous resource events are checked second
-        previousResourceEvents.findAndRemoveResourceEvent(resource, instanceId) match {
-          case some @ Some(_) ⇒
-            some
-          case _ ⇒
-            None
-        }
-    }
-  }
-
-  def updateIgnored(resourceEvent: ResourceEventModel): Unit = {
-    ignoredFirstResourceEvents.updateResourceEvent(resourceEvent)
-  }
-
-  def updatePrevious(resourceEvent: ResourceEventModel): Unit = {
-    previousResourceEvents.updateResourceEvent(resourceEvent)
-  }
-
-  def debugTheMaps(clog: ContextualLogger)(rcDebugInfo: ResourceEventModel ⇒ String): Unit = {
-    if(previousResourceEvents.size > 0) {
-      val map = previousResourceEvents.latestEventsMap.map { case (k, v) => (k, rcDebugInfo(v)) }
-      clog.debugMap("previousResourceEvents", map, 0)
-    }
-    if(implicitlyIssuedStartEvents.size > 0) {
-      val map = implicitlyIssuedStartEvents.implicitlyIssuedEventsMap.map { case (k, v) => (k, rcDebugInfo(v)) }
-      clog.debugMap("implicitlyTerminatedResourceEvents", map, 0)
-    }
-    if(ignoredFirstResourceEvents.size > 0) {
-      val map = ignoredFirstResourceEvents.ignoredFirstEventsMap.map { case (k, v) => (k, rcDebugInfo(v)) }
-      clog.debugMap("ignoredFirstResourceEvents", map, 0)
-    }
-  }
-
-//  private[this]
-//  def allPreviousAndAllImplicitlyStarted: List[ResourceEvent] = {
-//    val buffer: FullMutableResourceTypeMap = scala.collection.mutable.Map[FullResourceType, ResourceEvent]()
-//
-//    buffer ++= implicitlyIssuedStartEvents.implicitlyIssuedEventsMap
-//    buffer ++= previousResourceEvents.latestEventsMap
-//
-//    buffer.valuesIterator.toList
-//  }
-
-  /**
-   * Find those events from `implicitlyIssuedStartEvents` and `previousResourceEvents` that will generate implicit
-   * end events along with those implicitly issued events. Before returning, remove the events that generated the
-   * implicit ends from the internal state of this instance.
-   *
-   * @see [[gr.grnet.aquarium.logic.accounting.dsl.DSLCostPolicy]]
-   */
-  def findAndRemoveGeneratorsOfImplicitEndEvents(newOccuredMillis: Long
-                                                ): (List[ResourceEventModel], List[ResourceEventModel]) = {
-    val buffer = mutable.ListBuffer[(ResourceEventModel, ResourceEventModel)]()
-    val checkSet = mutable.Set[ResourceEventModel]()
-
-    def doItFor(map: ResourceEventModel.FullMutableResourceTypeMap): Unit = {
-      val resourceEvents = map.valuesIterator
-      for {
-        resourceEvent ← resourceEvents
-        dslResource   ← resourcesMap.findResource(resourceEvent.safeResource)
-        costPolicy    =  dslResource.costPolicy
-      } {
-        if(costPolicy.supportsImplicitEvents) {
-          if(costPolicy.mustConstructImplicitEndEventFor(resourceEvent)) {
-            val implicitEnd = costPolicy.constructImplicitEndEventFor(resourceEvent, newOccuredMillis)
-
-            if(!checkSet.contains(resourceEvent)) {
-              checkSet.add(resourceEvent)
-              buffer append ((resourceEvent, implicitEnd))
-            }
-
-            // remove it anyway
-            map.remove((resourceEvent.safeResource, resourceEvent.safeInstanceId))
-          }
-        }
-      }
-    }
-
-    doItFor(previousResourceEvents.latestEventsMap)                // we give priority for previous
-    doItFor(implicitlyIssuedStartEvents.implicitlyIssuedEventsMap) // ... over implicitly issued...
-
-    (buffer.view.map(_._1).toList, buffer.view.map(_._2).toList)
-  }
-}
-
-object UserStateWorker {
-  def fromUserState(userState: UserState, accounting: Accounting, resourcesMap: DSLResourcesMap): UserStateWorker = {
-    UserStateWorker(
-      userState.userID,
-      userState.latestResourceEventsSnapshot.toMutableWorker,
-      userState.implicitlyIssuedSnapshot.toMutableWorker,
-      IgnoredFirstResourceEventsWorker.Empty,
-      accounting,
-      resourcesMap
-    )
-  }
-}
diff --git a/src/main/scala/gr/grnet/aquarium/computation/UserStateWorker.scala b/src/main/scala/gr/grnet/aquarium/computation/UserStateWorker.scala
new file mode 100644 (file)
index 0000000..ab5abb3
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * 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.computation
+
+import scala.collection.mutable
+import gr.grnet.aquarium.logic.accounting.dsl.DSLResourcesMap
+import gr.grnet.aquarium.logic.accounting.Accounting
+import gr.grnet.aquarium.event.resource.ResourceEventModel
+import gr.grnet.aquarium.computation.data.{LatestResourceEventsWorker, ImplicitlyIssuedResourceEventsWorker, IgnoredFirstResourceEventsWorker}
+import gr.grnet.aquarium.util.ContextualLogger
+
+/**
+ * A helper object holding intermediate state/results during resource event processing.
+ *
+ * @param previousResourceEvents
+ * 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.
+ *
+ * @param implicitlyIssuedStartEvents
+ * The implicitly issued resource events at the beginning of the billing period.
+ *
+ * @param ignoredFirstResourceEvents
+ * The resource events that were first (and unused) of their kind.
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+case class UserStateWorker(userID: String,
+                           previousResourceEvents: LatestResourceEventsWorker,
+                           implicitlyIssuedStartEvents: ImplicitlyIssuedResourceEventsWorker,
+                           ignoredFirstResourceEvents: IgnoredFirstResourceEventsWorker,
+                           accounting: Accounting,
+                           resourcesMap: DSLResourcesMap) {
+
+  /**
+   * Finds the previous resource event by checking two possible sources: a) The implicitly terminated resource
+   * events and b) the explicit previous resource events. If the event is found, it is removed from the
+   * respective source.
+   *
+   * If the event is not found, then this must be for a new resource instance.
+   * (and probably then some `zero` resource event must be implied as the previous one)
+   *
+   * @param resource
+   * @param instanceId
+   * @return
+   */
+  def findAndRemovePreviousResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = {
+    // implicitly issued events are checked first
+    implicitlyIssuedStartEvents.findAndRemoveResourceEvent(resource, instanceId) match {
+      case some@Some(_) ⇒
+        some
+      case None ⇒
+        // explicit previous resource events are checked second
+        previousResourceEvents.findAndRemoveResourceEvent(resource, instanceId) match {
+          case some@Some(_) ⇒
+            some
+          case _ ⇒
+            None
+        }
+    }
+  }
+
+  def updateIgnored(resourceEvent: ResourceEventModel): Unit = {
+    ignoredFirstResourceEvents.updateResourceEvent(resourceEvent)
+  }
+
+  def updatePrevious(resourceEvent: ResourceEventModel): Unit = {
+    previousResourceEvents.updateResourceEvent(resourceEvent)
+  }
+
+  def debugTheMaps(clog: ContextualLogger)(rcDebugInfo: ResourceEventModel ⇒ String): Unit = {
+    if(previousResourceEvents.size > 0) {
+      val map = previousResourceEvents.latestEventsMap.map {
+        case (k, v) => (k, rcDebugInfo(v))
+      }
+      clog.debugMap("previousResourceEvents", map, 0)
+    }
+    if(implicitlyIssuedStartEvents.size > 0) {
+      val map = implicitlyIssuedStartEvents.implicitlyIssuedEventsMap.map {
+        case (k, v) => (k, rcDebugInfo(v))
+      }
+      clog.debugMap("implicitlyTerminatedResourceEvents", map, 0)
+    }
+    if(ignoredFirstResourceEvents.size > 0) {
+      val map = ignoredFirstResourceEvents.ignoredFirstEventsMap.map {
+        case (k, v) => (k, rcDebugInfo(v))
+      }
+      clog.debugMap("ignoredFirstResourceEvents", map, 0)
+    }
+  }
+
+  //  private[this]
+  //  def allPreviousAndAllImplicitlyStarted: List[ResourceEvent] = {
+  //    val buffer: FullMutableResourceTypeMap = scala.collection.mutable.Map[FullResourceType, ResourceEvent]()
+  //
+  //    buffer ++= implicitlyIssuedStartEvents.implicitlyIssuedEventsMap
+  //    buffer ++= previousResourceEvents.latestEventsMap
+  //
+  //    buffer.valuesIterator.toList
+  //  }
+
+  /**
+   * Find those events from `implicitlyIssuedStartEvents` and `previousResourceEvents` that will generate implicit
+   * end events along with those implicitly issued events. Before returning, remove the events that generated the
+   * implicit ends from the internal state of this instance.
+   *
+   * @see [[gr.grnet.aquarium.logic.accounting.dsl.DSLCostPolicy]]
+   */
+  def findAndRemoveGeneratorsOfImplicitEndEvents(newOccuredMillis: Long
+                                                  ): (List[ResourceEventModel], List[ResourceEventModel]) = {
+    val buffer = mutable.ListBuffer[(ResourceEventModel, ResourceEventModel)]()
+    val checkSet = mutable.Set[ResourceEventModel]()
+
+    def doItFor(map: ResourceEventModel.FullMutableResourceTypeMap): Unit = {
+      val resourceEvents = map.valuesIterator
+      for {
+        resourceEvent ← resourceEvents
+        dslResource ← resourcesMap.findResource(resourceEvent.safeResource)
+        costPolicy = dslResource.costPolicy
+      } {
+        if(costPolicy.supportsImplicitEvents) {
+          if(costPolicy.mustConstructImplicitEndEventFor(resourceEvent)) {
+            val implicitEnd = costPolicy.constructImplicitEndEventFor(resourceEvent, newOccuredMillis)
+
+            if(!checkSet.contains(resourceEvent)) {
+              checkSet.add(resourceEvent)
+              buffer append ((resourceEvent, implicitEnd))
+            }
+
+            // remove it anyway
+            map.remove((resourceEvent.safeResource, resourceEvent.safeInstanceId))
+          }
+        }
+      }
+    }
+
+    doItFor(previousResourceEvents.latestEventsMap) // we give priority for previous
+    doItFor(implicitlyIssuedStartEvents.implicitlyIssuedEventsMap) // ... over implicitly issued...
+
+    (buffer.view.map(_._1).toList, buffer.view.map(_._2).toList)
+  }
+}
+
+object UserStateWorker {
+  def fromUserState(userState: UserState, accounting: Accounting, resourcesMap: DSLResourcesMap): UserStateWorker = {
+    UserStateWorker(
+      userState.userID,
+      userState.latestResourceEventsSnapshot.toMutableWorker,
+      userState.implicitlyIssuedSnapshot.toMutableWorker,
+      IgnoredFirstResourceEventsWorker.Empty,
+      accounting,
+      resourcesMap
+    )
+  }
+}
diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/AgreementSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/AgreementSnapshot.scala
new file mode 100644 (file)
index 0000000..1fc29c9
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * 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.computation.data
+
+import gr.grnet.aquarium.util.date.MutableDateCalc
+import java.util.Date
+import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
+
+/**
+ * Represents an agreement valid for a specific amount of time. By convention,
+ * if an agreement is currently valid, then the validTo field is equal to `Long.MaxValue`.
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+case class AgreementSnapshot(name: String, validFrom: Long, validTo: Long = Long.MaxValue) {
+  require(validTo > validFrom)
+  require(!name.isEmpty)
+
+  def timeslot = Timeslot(new Date(validFrom), new Date(validTo))
+
+  override def toString =
+    "AgreementSnapshot(%s, %s, %s)".
+      format(name, new MutableDateCalc(validFrom), new MutableDateCalc(validTo))
+}
diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/AgreementsSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/AgreementsSnapshot.scala
new file mode 100644 (file)
index 0000000..a7c7860
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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.computation.data
+
+import java.util.Date
+
+import gr.grnet.aquarium.logic.accounting.dsl.{DSLAgreement, Timeslot}
+import gr.grnet.aquarium.logic.accounting.Policy
+import scala.collection.immutable.{SortedMap, TreeMap}
+
+/**
+ * User agreement data that will be part of UserState.
+ * The provided list of agreements cannot have time gaps. This is checked at object creation type.
+ *
+ * Note: This is copied from UserDataSnapshot.scala/AgreementSnapshot.
+ * TODO: Review
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+case class AgreementsSnapshot(agreements: List[AgreementSnapshot]) {
+  ensureNoGaps(agreements.sortWith((a,b) => if (b.validFrom > a.validFrom) true else false))
+
+  def ensureNoGaps(agreements: List[AgreementSnapshot]): Unit = agreements match {
+    case ha :: (t @ (hb :: tail)) =>
+      assert(ha.validTo - hb.validFrom == 1);
+      ensureNoGaps(t)
+    case h :: Nil =>
+      assert(h.validTo == Long.MaxValue)
+    case Nil => ()
+  }
+
+  def agreementsByTimeslot: SortedMap[Timeslot, String] = {
+    TreeMap(agreements.map(ag => (ag.timeslot, ag.name)): _*)
+  }
+
+  /**
+   * Get the user agreement at the specified timestamp
+   */
+  def findForTime(at: Long): Option[DSLAgreement] = {
+    // FIXME: Refactor and do not make this static call to Policy
+    agreements.find{ x => x.validFrom < at && x.validTo > at} match {
+      case Some(x) => Policy.policy(new Date(at)).findAgreement(x.name)
+      case None => None
+    }
+  }
+}
diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/CreditSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/CreditSnapshot.scala
new file mode 100644 (file)
index 0000000..7ec81d9
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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.computation.data
+
+
+/**
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+case class CreditSnapshot(creditAmount: Double)
diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/IMStateSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/IMStateSnapshot.scala
new file mode 100644 (file)
index 0000000..37efd53
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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.computation.data
+
+import gr.grnet.aquarium.event.im.IMEventModel
+
+/**
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+case class IMStateSnapshot(imEvent: IMEventModel)
diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsSnapshot.scala
new file mode 100644 (file)
index 0000000..f82c855
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * 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.computation.data
+
+import gr.grnet.aquarium.event.resource.ResourceEventModel
+
+/**
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+case class IgnoredFirstResourceEventsSnapshot(ignoredFirstEvents: List[ResourceEventModel]) {
+  def toMutableWorker = {
+    val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]()
+    for(ignoredFirstEvent <- ignoredFirstEvents) {
+      map(ignoredFirstEvent.fullResourceInfo) = ignoredFirstEvent
+    }
+
+    IgnoredFirstResourceEventsWorker(map)
+  }
+}
diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsWorker.scala b/src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsWorker.scala
new file mode 100644 (file)
index 0000000..7936784
--- /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.computation.data
+
+import gr.grnet.aquarium.util.findAndRemoveFromMap
+import gr.grnet.aquarium.event.resource.ResourceEventModel
+import gr.grnet.aquarium.event.resource.ResourceEventModel.FullMutableResourceTypeMap
+
+/**
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+case class IgnoredFirstResourceEventsWorker(ignoredFirstEventsMap: FullMutableResourceTypeMap) {
+  def toImmutableSnapshot(snapshotTime: Long) =
+    IgnoredFirstResourceEventsSnapshot(ignoredFirstEventsMap.valuesIterator.toList)
+
+  def findAndRemoveResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = {
+    findAndRemoveFromMap(ignoredFirstEventsMap, (resource, instanceId))
+  }
+
+  def updateResourceEvent(resourceEvent: ResourceEventModel): Unit = {
+    ignoredFirstEventsMap((resourceEvent.resource, resourceEvent.instanceID)) = resourceEvent
+  }
+
+  def size = ignoredFirstEventsMap.size
+
+  def foreach[U](f: ResourceEventModel => U): Unit = {
+    ignoredFirstEventsMap.valuesIterator.foreach(f)
+  }
+}
+
+object IgnoredFirstResourceEventsWorker {
+  final val Empty = IgnoredFirstResourceEventsWorker(scala.collection.mutable.Map())
+}
\ No newline at end of file
diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsSnapshot.scala
new file mode 100644 (file)
index 0000000..52b94fc
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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.computation.data
+
+import gr.grnet.aquarium.event.resource.ResourceEventModel
+
+/**
+ * Keeps the implicit OFF events when a billing period ends.
+ * This is normally recorded in the [[gr.grnet.aquarium.user.UserState]].
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+case class ImplicitlyIssuedResourceEventsSnapshot(implicitlyIssuedEvents: List[ResourceEventModel]) {
+  /**
+   * The gateway to playing with mutable state.
+   *
+   * @return A fresh instance of [[gr.grnet.aquarium.computation.data.ImplicitlyIssuedResourceEventsWorker]].
+   */
+  def toMutableWorker = {
+    val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]()
+    for(implicitEvent <- implicitlyIssuedEvents) {
+      map(implicitEvent.fullResourceInfo) = implicitEvent
+    }
+
+    ImplicitlyIssuedResourceEventsWorker(map)
+  }
+}
+
diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsWorker.scala b/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsWorker.scala
new file mode 100644 (file)
index 0000000..0aec78d
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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.computation.data
+
+import gr.grnet.aquarium.util.findAndRemoveFromMap
+import gr.grnet.aquarium.event.resource.ResourceEventModel
+import gr.grnet.aquarium.event.resource.ResourceEventModel.FullMutableResourceTypeMap
+
+
+/**
+ * This is the mutable cousin of [[gr.grnet.aquarium.computation.data.ImplicitlyIssuedResourceEventsSnapshot]].
+ *
+ * @param implicitlyIssuedEventsMap
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+case class ImplicitlyIssuedResourceEventsWorker(implicitlyIssuedEventsMap: FullMutableResourceTypeMap) {
+
+  def toList: scala.List[ResourceEventModel] = {
+    implicitlyIssuedEventsMap.valuesIterator.toList
+  }
+
+  def toImmutableSnapshot(snapshotTime: Long) =
+    ImplicitlyIssuedResourceEventsSnapshot(toList)
+
+  def findAndRemoveResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = {
+    findAndRemoveFromMap(implicitlyIssuedEventsMap, (resource, instanceId))
+  }
+
+  def size = implicitlyIssuedEventsMap.size
+
+  def foreach[U](f: ResourceEventModel => U): Unit = {
+    implicitlyIssuedEventsMap.valuesIterator.foreach(f)
+  }
+}
+
+object ImplicitlyIssuedResourceEventsWorker {
+  final val Empty = ImplicitlyIssuedResourceEventsWorker(scala.collection.mutable.Map())
+}
\ No newline at end of file
diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsSnapshot.scala
new file mode 100644 (file)
index 0000000..6c86f7f
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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.computation.data
+
+import gr.grnet.aquarium.event.resource.ResourceEventModel
+
+/**
+ * Keeps the latest resource event per resource instance.
+ *
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+case class LatestResourceEventsSnapshot(resourceEvents: List[ResourceEventModel]) {
+
+  /**
+   * The gateway to playing with mutable state.
+   *
+   * @return A fresh instance of [[gr.grnet.aquarium.computation.data.LatestResourceEventsWorker]].
+   */
+  def toMutableWorker = {
+    val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]()
+    for(latestEvent <- resourceEvents) {
+      map(latestEvent.fullResourceInfo) = latestEvent
+    }
+    LatestResourceEventsWorker(map)
+  }
+}
+
diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsWorker.scala b/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsWorker.scala
new file mode 100644 (file)
index 0000000..9690050
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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.computation.data
+
+import gr.grnet.aquarium.util.findAndRemoveFromMap
+import gr.grnet.aquarium.event.resource.ResourceEventModel
+import gr.grnet.aquarium.event.resource.ResourceEventModel.FullMutableResourceTypeMap
+
+/**
+ * This is the mutable cousin of [[gr.grnet.aquarium.computation.data.LatestResourceEventsSnapshot]].
+ *
+ * @param latestEventsMap
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+case class LatestResourceEventsWorker(latestEventsMap: FullMutableResourceTypeMap) {
+
+  /**
+   * The gateway to immutable state.
+   *
+   * @param snapshotTime The relevant snapshot time.
+   * @return A fresh instance of [[gr.grnet.aquarium.computation.data.LatestResourceEventsSnapshot]].
+   */
+  def toImmutableSnapshot(snapshotTime: Long) =
+    LatestResourceEventsSnapshot(latestEventsMap.valuesIterator.toList)
+
+  def updateResourceEvent(resourceEvent: ResourceEventModel): Unit = {
+    latestEventsMap((resourceEvent.resource, resourceEvent.instanceID)) = resourceEvent
+  }
+
+  def findResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = {
+    latestEventsMap.get((resource, instanceId))
+  }
+
+  def findAndRemoveResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = {
+    findAndRemoveFromMap(latestEventsMap, (resource, instanceId))
+  }
+
+  def size = latestEventsMap.size
+
+  def foreach[U](f: ResourceEventModel => U): Unit = {
+    latestEventsMap.valuesIterator.foreach(f)
+  }
+}
+
+object LatestResourceEventsWorker {
+  final val Empty = LatestResourceEventsWorker(scala.collection.mutable.Map())
+
+  /**
+   * Helper factory to construct a worker from a list of events.
+   */
+  def fromList(latestEventsList: List[ResourceEventModel]): LatestResourceEventsWorker = {
+    LatestResourceEventsSnapshot(latestEventsList).toMutableWorker
+  }
+}
+
diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesMap.scala b/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesMap.scala
new file mode 100644 (file)
index 0000000..c144456
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * 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.computation.data
+
+/**
+ * A map from (resourceName, resourceInstanceId) to value.
+ *
+ * This representation is convenient for computations and updating, while the
+ * [[gr.grnet.aquarium.computation.data.OwnedResourcesSnapshot]] representation is convenient for JSON serialization.
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+class OwnedResourcesMap(resourcesMap: Map[(String, String), Double]) {
+  def toResourcesSnapshot(snapshotTime: Long): OwnedResourcesSnapshot =
+    OwnedResourcesSnapshot(
+      resourcesMap map {
+        case ((name, instanceId), value) ⇒
+          ResourceInstanceSnapshot(name, instanceId, value) } toList
+    )
+}
\ No newline at end of file
diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesSnapshot.scala
new file mode 100644 (file)
index 0000000..fd7649c
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * 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.computation.data
+
+
+/**
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+case class OwnedResourcesSnapshot(resourceInstanceSnapshots: List[ResourceInstanceSnapshot]) {
+
+  def toResourcesMap: OwnedResourcesMap = {
+    val tuples = for(rc <- resourceInstanceSnapshots) yield ((rc.resource, rc.instanceId), (rc.instanceAmount))
+
+    new OwnedResourcesMap(Map(tuples.toSeq: _*))
+  }
+
+  def resourceInstanceSnapshotsExcept(resource: String, instanceId: String) = {
+    // Unfortunately, we have to use a List for data, since JSON serialization is not as flexible
+    // (at least out of the box). Thus, the update is O(L), where L is the length of the data List.
+    resourceInstanceSnapshots.filterNot(_.isSameResourceInstance(resource, instanceId))
+  }
+
+  def findResourceInstanceSnapshot(resource: String, instanceId: String): Option[ResourceInstanceSnapshot] = {
+    resourceInstanceSnapshots.find(x => resource == x.resource && instanceId == x.instanceId)
+  }
+
+  def getResourceInstanceAmount(resource: String, instanceId: String, defaultValue: Double): Double = {
+    findResourceInstanceSnapshot(resource, instanceId).map(_.instanceAmount).getOrElse(defaultValue)
+  }
+
+  def computeResourcesSnapshotUpdate(resource: String,   // resource name
+                                     instanceId: String, // resource instance id
+                                     newAmount: Double,
+                                     snapshotTime: Long): (OwnedResourcesSnapshot,
+                                                          Option[ResourceInstanceSnapshot],
+    ResourceInstanceSnapshot) = {
+
+    val newResourceInstance = ResourceInstanceSnapshot(resource, instanceId, newAmount)
+    val oldResourceInstanceOpt = this.findResourceInstanceSnapshot(resource, instanceId)
+
+    val newResourceInstances = oldResourceInstanceOpt match {
+      case Some(oldResourceInstance) ⇒
+        // Resource instance found, so delete the old one and add the new one
+        newResourceInstance :: resourceInstanceSnapshotsExcept(resource, instanceId)
+
+      case None ⇒
+        // Resource not found, so this is the first time and we just add the new snapshot
+        newResourceInstance :: resourceInstanceSnapshots
+    }
+
+    val newOwnedResources = OwnedResourcesSnapshot(newResourceInstances)
+
+    (newOwnedResources, oldResourceInstanceOpt, newResourceInstance)
+ }
+}
\ No newline at end of file
diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/ResourceInstanceSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/ResourceInstanceSnapshot.scala
new file mode 100644 (file)
index 0000000..70cca44
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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.computation.data
+
+/**
+ * Maintains the current state of a resource instance owned by the user.
+ * The encoding is as follows:
+ *
+ * name: DSLResource.name
+ * instanceId: instance-id (in the resource's descriminatorField)
+ * data: current-resource-value
+ * snapshotTime: last-update-timestamp
+ *
+ * In order to have a uniform representation of the resource state for all
+ * resource types (complex or simple) the following convention applies:
+ *
+ *  - If the resource is complex, the (name, instanceId) is (DSLResource.name, instance-id)
+ *  - If the resource is simple,  the (name, instanceId) is (DSLResource.name, "1")
+ *
+ * @param resource        Same as `resource` of [[gr.grnet.aquarium.event.resource.ResourceEventModel]]
+ * @param instanceId      Same as `instanceId` of [[gr.grnet.aquarium.event.resource.ResourceEventModel]]
+ * @param instanceAmount  This is the amount kept for the resource instance.
+*                         The general rule is that an amount saved in a [[gr.grnet.aquarium.computation.data. ResourceInstanceSnapshot]]
+ *                        represents a total value, while a value appearing in a [[gr.grnet.aquarium.event.resource.ResourceEventModel]]
+ *                        represents a difference. How these two values are combined to form the new amount is dictated
+ *                        by the underlying [[gr.grnet.aquarium.logic.accounting.dsl.DSLCostPolicy]]
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+case class ResourceInstanceSnapshot(resource: String,
+                                    instanceId: String,
+                                    instanceAmount: Double) {
+
+  def isSameResourceInstance(resource: String, instanceId: String) = {
+    this.resource == resource &&
+    this.instanceId == instanceId
+  }
+}
diff --git a/src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReason.scala b/src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReason.scala
new file mode 100644 (file)
index 0000000..556df83
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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.computation.reason
+
+import gr.grnet.aquarium.computation.BillingMonthInfo
+import gr.grnet.aquarium.event.im.IMEventModel
+
+sealed trait UserStateChangeReason {
+  /**
+      * Return `true` if the result of the calculation should be stored back to the
+      * [[gr.grnet.aquarium.store.UserStateStore]].
+      *
+      */
+     def shouldStoreUserState: Boolean
+
+  def shouldStoreCalculatedWalletEntries: Boolean
+
+  def forPreviousBillingMonth: UserStateChangeReason
+
+  def calculateCreditsForImplicitlyTerminated: Boolean
+
+  def code: UserStateChangeReasonCodes.ChangeReasonCode
+}
+
+case object InitialUserStateSetup extends UserStateChangeReason {
+  def shouldStoreUserState = true
+
+  def shouldStoreCalculatedWalletEntries = false
+
+  def forPreviousBillingMonth = this
+
+  def calculateCreditsForImplicitlyTerminated = false
+
+  def code = UserStateChangeReasonCodes.InitialSetupCode
+}
+/**
+ * A calculation made for no specific reason. Can be for testing, for example.
+ *
+ */
+case object NoSpecificChangeReason extends UserStateChangeReason {
+  def shouldStoreUserState = false
+
+  def shouldStoreCalculatedWalletEntries = false
+
+  def forBillingMonthInfo(bmi: BillingMonthInfo) = this
+
+  def forPreviousBillingMonth = this
+
+  def calculateCreditsForImplicitlyTerminated = false
+
+  def code = UserStateChangeReasonCodes.NoSpecificChangeCode
+}
+
+/**
+ * An authoritative calculation for the billing period.
+ *
+ * This marks a state for caching.
+ *
+ * @param billingMonthInfo
+ */
+case class MonthlyBillingCalculation(billingMonthInfo: BillingMonthInfo) extends UserStateChangeReason {
+  def shouldStoreUserState = true
+
+  def shouldStoreCalculatedWalletEntries = true
+
+  def forPreviousBillingMonth = MonthlyBillingCalculation(billingMonthInfo.previousMonth)
+
+  def calculateCreditsForImplicitlyTerminated = true
+
+  def code = UserStateChangeReasonCodes.MonthlyBillingCode
+}
+
+/**
+ * Used for the realtime billing calculation.
+ *
+ * @param forWhenMillis The time this calculation is for
+ */
+case class RealtimeBillingCalculation(forWhenMillis: Long) extends UserStateChangeReason {
+  def shouldStoreUserState = false
+
+  def shouldStoreCalculatedWalletEntries = false
+
+  def forPreviousBillingMonth = this
+
+  def calculateCreditsForImplicitlyTerminated = false
+
+  def code = UserStateChangeReasonCodes.RealtimeBillingCode
+}
+
+case class IMEventArrival(imEvent: IMEventModel) extends UserStateChangeReason {
+  def shouldStoreUserState = true
+
+  def shouldStoreCalculatedWalletEntries = false
+
+  def forPreviousBillingMonth = this
+
+  def calculateCreditsForImplicitlyTerminated = false
+
+  def code = UserStateChangeReasonCodes.IMEventArrivalCode
+}
diff --git a/src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReasonCodes.scala b/src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReasonCodes.scala
new file mode 100644 (file)
index 0000000..9d0e3cc
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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.computation.reason
+
+/**
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+object UserStateChangeReasonCodes {
+  type ChangeReasonCode = Int
+
+  final val InitialSetupCode = 1
+  final val NoSpecificChangeCode = 2
+  final val MonthlyBillingCode = 3
+  final val RealtimeBillingCode = 4
+  final val IMEventArrivalCode = 5
+}
index b376bff..60726a8 100644 (file)
@@ -261,7 +261,7 @@ trait Accounting extends DSLUtils with Loggable {
                              newTotalAmount: Double,
                              dslResource: DSLResource,
                              defaultResourceMap: DSLResourcesMap,
-                             agreementNamesByTimeslot: Map[Timeslot, String],
+                             agreementNamesByTimeslot: SortedMap[Timeslot, String],
                              algorithmCompiler: CostPolicyAlgorithmCompiler,
                              policyStore: PolicyStore,
                              clogOpt: Option[ContextualLogger] = None): (Timeslot, List[Chargeslot]) = {
@@ -480,7 +480,7 @@ trait Accounting extends DSLUtils with Loggable {
     if (previousOccurred.getTime == event.occurredMillis) {
       dslResource.costPolicy match {
         case DiscreteCostPolicy => //Ok
-        case _ => return Some(List())
+        case _ => return Just(List())
       }
     }
 
index 2760a9e..add6394 100644 (file)
 
 package gr.grnet.aquarium.store
 
-import gr.grnet.aquarium.user.UserState
-import com.ckkloverdos.maybe.Maybe
+import gr.grnet.aquarium.computation.UserState
 
 /**
  * A store for user state snapshots.
  *
- * This is used to hold snapshots of [[gr.grnet.aquarium.user.UserState]]
+ * This is used to hold snapshots of [[gr.grnet.aquarium.computation.UserState]]
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
index 985a27e..8adb68c 100644 (file)
@@ -42,15 +42,13 @@ import scala.collection.JavaConversions._
 import java.util.Date
 import collection.mutable.ConcurrentMap
 import java.util.concurrent.ConcurrentHashMap
-import gr.grnet.aquarium.user.UserState
 import gr.grnet.aquarium.uid.ConcurrentVMLocalUIDGenerator
-import gr.grnet.aquarium.{AquariumException, Configurable}
+import gr.grnet.aquarium.Configurable
 import gr.grnet.aquarium.event.{WalletEntry, PolicyEntry}
-import gr.grnet.aquarium.converter.JsonTextFormat
-import gr.grnet.aquarium.util._
 import gr.grnet.aquarium.event.im.{StdIMEvent, IMEventModel}
 import org.bson.types.ObjectId
 import gr.grnet.aquarium.event.resource.{StdResourceEvent, ResourceEventModel}
+import gr.grnet.aquarium.computation.UserState
 
 /**
  * An implementation of various stores that persists data in memory.
@@ -122,7 +120,7 @@ class MemStore extends UserStateStore
 
     goodOnes.sortWith {
       case (us1, us2) ⇒
-        us1.oldestSnapshotTime > us2.oldestSnapshotTime
+        us1.occurredMillis > us2.occurredMillis
     } match {
       case head :: _ ⇒
         Some(head)
@@ -147,7 +145,7 @@ class MemStore extends UserStateStore
     
     goodOnes.sortWith {
       case (us1, us2) ⇒
-        us1.oldestSnapshotTime > us2.oldestSnapshotTime
+        us1.occurredMillis > us2.occurredMillis
     } match {
       case head :: _ ⇒
         Some(head)
index a8671ec..870fd13 100644 (file)
@@ -36,8 +36,7 @@
 package gr.grnet.aquarium.store.mongodb
 
 import com.mongodb.util.JSON
-import gr.grnet.aquarium.user.UserState
-import gr.grnet.aquarium.user.UserState.{JsonNames ⇒ UserStateJsonNames}
+import gr.grnet.aquarium.computation.UserState.{JsonNames ⇒ UserStateJsonNames}
 import gr.grnet.aquarium.util.json.JsonSupport
 import collection.mutable.ListBuffer
 import gr.grnet.aquarium.event._
@@ -55,6 +54,7 @@ import org.bson.types.ObjectId
 import com.ckkloverdos.maybe.Maybe
 import gr.grnet.aquarium.util._
 import gr.grnet.aquarium.converter.Conversions
+import gr.grnet.aquarium.computation.UserState
 
 /**
  * Mongodb implementation of the various aquarium stores.
@@ -378,9 +378,9 @@ object MongoDBStore {
   final val RESOURCE_EVENTS_COLLECTION = "resevents"
 
   /**
-   * Collection holding the snapshots of [[gr.grnet.aquarium.user.UserState]].
+   * Collection holding the snapshots of [[gr.grnet.aquarium.computation.UserState]].
    *
-   * [[gr.grnet.aquarium.user.UserState]] is held internally within [[gr.grnet.aquarium.actor.service.user .UserActor]]s.
+   * [[gr.grnet.aquarium.computation.UserState]] is held internally within [[gr.grnet.aquarium.actor.service.user.UserActor]]s.
    */
   final val USER_STATES_COLLECTION = "userstates"
 
diff --git a/src/main/scala/gr/grnet/aquarium/user/UserDataSnapshot.scala b/src/main/scala/gr/grnet/aquarium/user/UserDataSnapshot.scala
deleted file mode 100644 (file)
index bd2d5b9..0000000
+++ /dev/null
@@ -1,423 +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
-package user
-
-import gr.grnet.aquarium.util.{findFromMapAsMaybe, findAndRemoveFromMap, shortClassNameOf}
-import gr.grnet.aquarium.logic.accounting.Policy
-import java.util.Date
-import com.ckkloverdos.maybe.{NoVal, Maybe, Just}
-import gr.grnet.aquarium.event.resource.ResourceEventModel.FullMutableResourceTypeMap
-import logic.accounting.dsl.{Timeslot, DSLAgreement}
-import collection.immutable.{TreeMap, SortedMap}
-import util.date.MutableDateCalc
-import event.resource.ResourceEventModel
-import gr.grnet.aquarium.event.im.IMEventModel
-
-/**
- * Snapshot of data that are user-related.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-
-case class CreditSnapshot(creditAmount: Double, snapshotTime: Long) extends DataSnapshot
-
-case class IMStateSnapshot(imEvent: IMEventModel, snapshotTime: Long) extends DataSnapshot
-
-//case class RolesSnapshot(roles: List[String], snapshotTime: Long) extends DataSnapshot
-
-/**
- * Represents an agreement valid for a specific amount of time. By convention,
- * if an agreement is currently valid, then the validTo field is equal to `Long.MaxValue`.
- */
-case class Agreement(name: String, validFrom: Long, validTo: Long = Long.MaxValue) {
-  assert(validTo > validFrom)
-  assert(!name.isEmpty)
-
-//  Policy.policy(new Date(validFrom)) match {
-//    case Just(x) => x.findAgreement(agreement) match {
-//      case None => assert(false)
-//      case _ =>
-//    }
-//    case _ => assert(false)
-//  }
-  
-  def timeslot = Timeslot(new Date(validFrom), new Date(validTo))
-
-  override def toString =
-    "Agreement(%s, %s, %s)".
-      format(name, new MutableDateCalc(validFrom), new MutableDateCalc(validTo))
-}
-
-/**
- * All user agreements. The provided list of agreements cannot have time gaps. This
- * is checked at object creation type.
- */
-case class AgreementSnapshot(agreements: List[Agreement], snapshotTime: Long) extends DataSnapshot {
-
-  ensureNoGaps(agreements.sortWith((a,b) => if (b.validFrom > a.validFrom) true else false))
-
-  def agreementsByTimeslot: SortedMap[Timeslot, String] = {
-    TreeMap(agreements.map(ag => (ag.timeslot, ag.name)): _*)
-  }
-
-  def ensureNoGaps(agreements: List[Agreement]): Unit = agreements match {
-    case ha :: (t @ (hb :: tail)) =>
-      assert(ha.validTo - hb.validFrom == 1);
-      ensureNoGaps(t)
-    case h :: Nil =>
-      assert(h.validTo == Long.MaxValue)
-    case Nil => ()
-  }
-
-  /**
-   * Get the user agreement at the specified timestamp
-   */
-  def getAgreement(at: Long): Maybe[DSLAgreement] =
-    agreements.find{ x => x.validFrom < at && x.validTo > at} match {
-      case Some(x) => Policy.policy(new Date(at)).findAgreement(x.name) match {
-          case Some(z) => Just(z)
-          case None => NoVal
-        }
-      case None => NoVal
-    }
-
-  override def toString = {
-    "%s(%s, %s)".format(shortClassNameOf(this), agreements, new MutableDateCalc(snapshotTime).toString)
-  }
-}
-
-/**
- * Maintains the current state of a resource instance owned by the user.
- * The encoding is as follows:
- *
- * name: DSLResource.name
- * instanceId: instance-id (in the resource's descriminatorField)
- * data: current-resource-value
- * snapshotTime: last-update-timestamp
- *
- * In order to have a uniform representation of the resource state for all
- * resource types (complex or simple) the following convention applies:
- *
- *  - If the resource is complex, the (name, instanceId) is (DSLResource.name, instance-id)
- *  - If the resource is simple,  the (name, instanceId) is (DSLResource.name, "1")
- *
- * @param resource        Same as `resource` of [[gr.grnet.aquarium.event.resource.ResourceEventModel]]
- * @param instanceId      Same as `instanceId` of [[gr.grnet.aquarium.event.resource.ResourceEventModel]]
- * @param instanceAmount  This is the amount kept for the resource instance.
-*                         The general rule is that an amount saved in a [[gr.grnet.aquarium.user.ResourceInstanceSnapshot]]
- *                        represents a total value, while a value appearing in a [[gr.grnet.aquarium.event.resource.ResourceEventModel]]
- *                        represents a difference. How these two values are combined to form the new amount is dictated
- *                        by the underlying [[gr.grnet.aquarium.logic.accounting.dsl.DSLCostPolicy]]
- * @param snapshotTime
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case class ResourceInstanceSnapshot(resource: String,
-                                    instanceId: String,
-                                    instanceAmount: Double,
-                                    snapshotTime: Long) extends DataSnapshot {
-
-  def isSameResourceInstance(resource: String, instanceId: String) = {
-    this.resource == resource &&
-    this.instanceId == instanceId
-  }
-}
-
-/**
- * A map from (resourceName, resourceInstanceId) to (value, snapshotTime).
- * This representation is convenient for computations and updating, while the
- * [[gr.grnet.aquarium.user.OwnedResourcesSnapshot]] representation is convenient for JSON serialization.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-class OwnedResourcesMap(resourcesMap: Map[(String, String), (Double, Long)]) {
-  def toResourcesSnapshot(snapshotTime: Long): OwnedResourcesSnapshot =
-    OwnedResourcesSnapshot(
-      resourcesMap map {
-        case ((name, instanceId), (value, snapshotTime)) ⇒
-          ResourceInstanceSnapshot(name, instanceId, value, snapshotTime
-      )} toList,
-      snapshotTime
-    )
-}
-
-/**
- *
- * @param resourceInstanceSnapshots
- * @param snapshotTime
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case class OwnedResourcesSnapshot(resourceInstanceSnapshots: List[ResourceInstanceSnapshot], snapshotTime: Long)
-  extends DataSnapshot {
-
-  def toResourcesMap: OwnedResourcesMap = {
-    val tuples = for(rc <- resourceInstanceSnapshots) yield ((rc.resource, rc.instanceId), (rc.instanceAmount, rc.snapshotTime))
-
-    new OwnedResourcesMap(Map(tuples.toSeq: _*))
-  }
-
-  def resourceInstanceSnapshotsExcept(resource: String, instanceId: String) = {
-    // Unfortunately, we have to use a List for data, since JSON serialization is not as flexible
-    // (at least out of the box). Thus, the update is O(L), where L is the length of the data List.
-    resourceInstanceSnapshots.filterNot(_.isSameResourceInstance(resource, instanceId))
-  }
-
-  def findResourceInstanceSnapshot(resource: String, instanceId: String): Option[ResourceInstanceSnapshot] = {
-    resourceInstanceSnapshots.find(x => resource == x.resource && instanceId == x.instanceId)
-  }
-
-  def getResourceInstanceAmount(resource: String, instanceId: String, defaultValue: Double): Double = {
-    findResourceInstanceSnapshot(resource, instanceId).map(_.instanceAmount).getOrElse(defaultValue)
-  }
-
-  def computeResourcesSnapshotUpdate(resource: String,   // resource name
-                                     instanceId: String, // resource instance id
-                                     newAmount: Double,
-                                     snapshotTime: Long): (OwnedResourcesSnapshot,
-                                                          Option[ResourceInstanceSnapshot],
-                                                          ResourceInstanceSnapshot) = {
-
-    val newResourceInstance = ResourceInstanceSnapshot(resource, instanceId, newAmount, snapshotTime)
-    val oldResourceInstanceOpt = this.findResourceInstanceSnapshot(resource, instanceId)
-
-    val newResourceInstances = oldResourceInstanceOpt match {
-      case Some(oldResourceInstance) ⇒
-        // Resource instance found, so delete the old one and add the new one
-        newResourceInstance :: resourceInstanceSnapshotsExcept(resource, instanceId)
-      case None ⇒
-        // Resource not found, so this is the first time and we just add the new snapshot
-        newResourceInstance :: resourceInstanceSnapshots
-    }
-
-    val newOwnedResources = OwnedResourcesSnapshot(newResourceInstances, snapshotTime)
-
-    (newOwnedResources, oldResourceInstanceOpt, newResourceInstance)
- }
-}
-
-
-/**
- * A generic exception thrown when errors occur in dealing with user data snapshots
- *
- * @author Georgios Gousios <gousiosg@gmail.com>
- */
-class DataSnapshotException(msg: String) extends Exception(msg)
-
-/**
- * Holds the user active/suspended status.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-//case class ActiveStateSnapshot(isActive: Boolean, snapshotTime: Long) extends DataSnapshot
-
-/**
- * Keeps the latest resource event per resource instance.
- *
- * @param resourceEvents
- * @param snapshotTime
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case class LatestResourceEventsSnapshot(resourceEvents: List[ResourceEventModel],
-                                        snapshotTime: Long) extends DataSnapshot {
-
-  /**
-   * The gateway to playing with mutable state.
-   *
-   * @return A fresh instance of [[gr.grnet.aquarium.user.LatestResourceEventsWorker]].
-   */
-  def toMutableWorker = {
-    val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]()
-    for(latestEvent <- resourceEvents) {
-      map(latestEvent.fullResourceInfo) = latestEvent
-    }
-    LatestResourceEventsWorker(map)
-  }
-}
-
-/**
- * This is the mutable cousin of [[gr.grnet.aquarium.user.LatestResourceEventsSnapshot]].
- *
- * @param latestEventsMap
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case class LatestResourceEventsWorker(latestEventsMap: FullMutableResourceTypeMap) {
-
-  /**
-   * The gateway to immutable state.
-   *
-   * @param snapshotTime The relevant snapshot time.
-   * @return A fresh instance of [[gr.grnet.aquarium.user.LatestResourceEventsSnapshot]].
-   */
-  def toImmutableSnapshot(snapshotTime: Long) =
-    LatestResourceEventsSnapshot(latestEventsMap.valuesIterator.toList, snapshotTime)
-
-  def updateResourceEvent(resourceEvent: ResourceEventModel): Unit = {
-    latestEventsMap((resourceEvent.resource, resourceEvent.instanceID)) = resourceEvent
-  }
-  
-  def findResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = {
-    latestEventsMap.get((resource, instanceId))
-  }
-
-  def findAndRemoveResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = {
-    findAndRemoveFromMap(latestEventsMap, (resource, instanceId))
-  }
-
-  def size = latestEventsMap.size
-
-  def foreach[U](f: ResourceEventModel => U): Unit = {
-    latestEventsMap.valuesIterator.foreach(f)
-  }
-}
-
-object LatestResourceEventsWorker {
-  final val Empty = LatestResourceEventsWorker(scala.collection.mutable.Map())
-
-  /**
-   * Helper factory to construct a worker from a list of events.
-   */
-  def fromList(latestEventsList: List[ResourceEventModel]): LatestResourceEventsWorker = {
-    LatestResourceEventsSnapshot(latestEventsList, 0L).toMutableWorker
-  }
-}
-
-/**
- * Keeps the implicit OFF events when a billing period ends.
- * This is normally recorded in the [[gr.grnet.aquarium.user.UserState]].
- *
- * @param implicitlyIssuedEvents
- * @param snapshotTime
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case class ImplicitlyIssuedResourceEventsSnapshot(implicitlyIssuedEvents: List[ResourceEventModel],
-                                                  snapshotTime: Long) extends DataSnapshot {
-  /**
-   * The gateway to playing with mutable state.
-   *
-   * @return A fresh instance of [[gr.grnet.aquarium.user.ImplicitlyIssuedResourceEventsWorker]].
-   */
-  def toMutableWorker = {
-    val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]()
-    for(implicitEvent <- implicitlyIssuedEvents) {
-      map(implicitEvent.fullResourceInfo) = implicitEvent
-    }
-
-    ImplicitlyIssuedResourceEventsWorker(map)
-  }
-}
-
-/**
- * This is the mutable cousin of [[gr.grnet.aquarium.user.ImplicitlyIssuedResourceEventsSnapshot]].
- *
- * @param implicitlyIssuedEventsMap
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-case class ImplicitlyIssuedResourceEventsWorker(implicitlyIssuedEventsMap: FullMutableResourceTypeMap) {
-
-  def toList: scala.List[ResourceEventModel] = {
-    implicitlyIssuedEventsMap.valuesIterator.toList
-  }
-
-  def toImmutableSnapshot(snapshotTime: Long) =
-    ImplicitlyIssuedResourceEventsSnapshot(toList, snapshotTime)
-
-  def findAndRemoveResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = {
-    findAndRemoveFromMap(implicitlyIssuedEventsMap, (resource, instanceId))
-  }
-
-  def size = implicitlyIssuedEventsMap.size
-
-  def foreach[U](f: ResourceEventModel => U): Unit = {
-    implicitlyIssuedEventsMap.valuesIterator.foreach(f)
-  }
-}
-
-object ImplicitlyIssuedResourceEventsWorker {
-  final val Empty = ImplicitlyIssuedResourceEventsWorker(scala.collection.mutable.Map())
-}
-
-/**
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- *
- * @param ignoredFirstEvents
- * @param snapshotTime
- */
-case class IgnoredFirstResourceEventsSnapshot(ignoredFirstEvents: List[ResourceEventModel],
-                                              snapshotTime: Long) extends DataSnapshot {
-  def toMutableWorker = {
-    val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]()
-    for(ignoredFirstEvent <- ignoredFirstEvents) {
-      map(ignoredFirstEvent.fullResourceInfo) = ignoredFirstEvent
-    }
-
-    IgnoredFirstResourceEventsWorker(map)
-  }
-}
-
-/**
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- * @param ignoredFirstEventsMap
- */
-case class IgnoredFirstResourceEventsWorker(ignoredFirstEventsMap: FullMutableResourceTypeMap) {
-  def toImmutableSnapshot(snapshotTime: Long) =
-    IgnoredFirstResourceEventsSnapshot(ignoredFirstEventsMap.valuesIterator.toList, snapshotTime)
-
-  def findAndRemoveResourceEvent(resource: String, instanceId: String): Maybe[ResourceEventModel] = {
-    findAndRemoveFromMap(ignoredFirstEventsMap, (resource, instanceId))
-  }
-
-  def updateResourceEvent(resourceEvent: ResourceEventModel): Unit = {
-    ignoredFirstEventsMap((resourceEvent.resource, resourceEvent.instanceID)) = resourceEvent
-  }
-
-  def size = ignoredFirstEventsMap.size
-
-  def foreach[U](f: ResourceEventModel => U): Unit = {
-    ignoredFirstEventsMap.valuesIterator.foreach(f)
-  }
-}
-
-object IgnoredFirstResourceEventsWorker {
-  final val Empty = IgnoredFirstResourceEventsWorker(scala.collection.mutable.Map())
-}
\ No newline at end of file
index 47b2971..d57a9b2 100644 (file)
@@ -36,7 +36,7 @@
 package gr.grnet.aquarium.util
 
 import org.slf4j.Logger
-import com.ckkloverdos.maybe.{Failed, Just, Maybe}
+import com.ckkloverdos.maybe.Failed
 
 /**
  * A logger that keeps track of working context and indentation level.
@@ -46,58 +46,50 @@ import com.ckkloverdos.maybe.{Failed, Just, Maybe}
   * A sample output follows:
   *
   * {{{
- [DEBUG] 2012-02-13 12:54:53,653 main -                 doFullMonthlyBilling(2012-01) BEGIN
- [DEBUG] 2012-02-13 12:54:53,653 main -     findUserStateAtEndOfBillingMonth(2011-12)   BEGIN
- [DEBUG] 2012-02-13 12:54:53,661 main -     findUserStateAtEndOfBillingMonth(2011-12)     Found 0 out of sync events, will have to (re)compute user state
- [DEBUG] 2012-02-13 12:54:53,661 main -     findUserStateAtEndOfBillingMonth(2011-12)     Computing full month billing
- [DEBUG] 2012-02-13 12:54:53,662 main -                 doFullMonthlyBilling(2011-12)     BEGIN
- [DEBUG] 2012-02-13 12:54:53,662 main -     findUserStateAtEndOfBillingMonth(2011-11)       BEGIN
- [DEBUG] 2012-02-13 12:54:53,663 main -     findUserStateAtEndOfBillingMonth(2011-11)         Found 0 out of sync events, will have to (re)compute user state
- [DEBUG] 2012-02-13 12:54:53,663 main -     findUserStateAtEndOfBillingMonth(2011-11)         Computing full month billing
- [DEBUG] 2012-02-13 12:54:53,664 main -                 doFullMonthlyBilling(2011-11)         BEGIN
- [DEBUG] 2012-02-13 12:54:53,664 main -     findUserStateAtEndOfBillingMonth(2011-10)           BEGIN
- [DEBUG] 2012-02-13 12:54:53,667 main -     findUserStateAtEndOfBillingMonth(2011-10)             User did not exist before 2011-11-01 00:00:00.000. Returning UserState(Christos,0,0,false,null,ImplicitlyIssuedResourceEventsSnapshot(Map(),0),List(),List(),LatestResourceEventsSnapshot(Map(),0),0,ActiveStateSnapshot(false,0),CreditSnapshot(0.0,0),AgreementSnapshot(List(Agreement(default,0,-1)),0),RolesSnapshot(List(),0),OwnedResourcesSnapshot(List(),0))
- [DEBUG] 2012-02-13 12:54:53,668 main -     findUserStateAtEndOfBillingMonth(2011-10)           END
- [DEBUG] 2012-02-13 12:54:53,672 main -                 doFullMonthlyBilling(2011-11)           previousResourceEvents = LatestResourceEventsWorker(Map())
- [DEBUG] 2012-02-13 12:54:53,673 main -                 doFullMonthlyBilling(2011-11)           theImplicitOFFs = ImplicitlyIssuedResourceEventsWorker(Map())
- [DEBUG] 2012-02-13 12:54:53,680 main -                 doFullMonthlyBilling(2011-11)           resourceEventStore = MemStore(Map(UserState -> 0, WalletEntry -> 0, ResourceEvent -> 5, PolicyEntry -> 0, IMEvent -> 0))
- [DEBUG] 2012-02-13 12:54:53,681 main -                 doFullMonthlyBilling(2011-11)           Found 0 resource events, starting processing...
- [DEBUG] 2012-02-13 12:54:53,683 main -                 doFullMonthlyBilling(2011-11)         END
- [DEBUG] 2012-02-13 12:54:53,683 main -     findUserStateAtEndOfBillingMonth(2011-11)       END
- [DEBUG] 2012-02-13 12:54:53,684 main -                 doFullMonthlyBilling(2011-12)       previousResourceEvents = LatestResourceEventsWorker(Map())
- [DEBUG] 2012-02-13 12:54:53,684 main -                 doFullMonthlyBilling(2011-12)       theImplicitOFFs = ImplicitlyIssuedResourceEventsWorker(Map())
- [DEBUG] 2012-02-13 12:54:53,685 main -                 doFullMonthlyBilling(2011-12)       resourceEventStore = MemStore(Map(UserState -> 0, WalletEntry -> 0, ResourceEvent -> 5, PolicyEntry -> 0, IMEvent -> 0))
- [DEBUG] 2012-02-13 12:54:53,686 main -                 doFullMonthlyBilling(2011-12)       Found 0 resource events, starting processing...
- [DEBUG] 2012-02-13 12:54:53,686 main -                 doFullMonthlyBilling(2011-12)     END
- [DEBUG] 2012-02-13 12:54:53,687 main -     findUserStateAtEndOfBillingMonth(2011-12)   END
- [DEBUG] 2012-02-13 12:54:53,687 main -                 doFullMonthlyBilling(2012-01)   previousResourceEvents = LatestResourceEventsWorker(Map())
- [DEBUG] 2012-02-13 12:54:53,688 main -                 doFullMonthlyBilling(2012-01)   theImplicitOFFs = ImplicitlyIssuedResourceEventsWorker(Map())
- [DEBUG] 2012-02-13 12:54:53,688 main -                 doFullMonthlyBilling(2012-01)   resourceEventStore = MemStore(Map(UserState -> 0, WalletEntry -> 0, ResourceEvent -> 5, PolicyEntry -> 0, IMEvent -> 0))
- [DEBUG] 2012-02-13 12:54:53,689 main -                 doFullMonthlyBilling(2012-01)   Found 4 resource events, starting processing...
- [DEBUG] 2012-02-13 12:54:53,690 main -                 doFullMonthlyBilling(2012-01)     Processing EVENT(2, [2012-01-01 03:00:00.000], 99.0 [MB/Hr], diskspace::pithos/diskspace/DISK.1, Map(), Christos, pithos)
- [DEBUG] 2012-02-13 12:54:53,691 main -                 doFullMonthlyBilling(2012-01)       0 previousResourceEvents
- [DEBUG] 2012-02-13 12:54:53,691 main -                 doFullMonthlyBilling(2012-01)       0 theImplicitOFFs
- [DEBUG] 2012-02-13 12:54:53,694 main -                 doFullMonthlyBilling(2012-01)       Processing: ResourceEvent(2,1325379600000,1325379600000,Christos,pithos,diskspace,pithos/diskspace/DISK.1,1.0,99.0,Map())
- [DEBUG] 2012-02-13 12:54:53,704 main -                 doFullMonthlyBilling(2012-01)     Processing EVENT(0, [2012-01-02 01:00:00.000], ON, vmtime::synnefo/vmtime/VM.1, Map(), Christos, synnefo)
- [DEBUG] 2012-02-13 12:54:53,705 main -                 doFullMonthlyBilling(2012-01)       1 previousResourceEvents
- [DEBUG] 2012-02-13 12:54:53,708 main -                 doFullMonthlyBilling(2012-01)         EVENT(2, [2012-01-01 03:00:00.000], 99.0 [MB/Hr], diskspace::pithos/diskspace/DISK.1, Map(), Christos, pithos)
- [DEBUG] 2012-02-13 12:54:53,709 main -                 doFullMonthlyBilling(2012-01)       0 theImplicitOFFs
- [DEBUG] 2012-02-13 12:54:53,709 main -                 doFullMonthlyBilling(2012-01)       Ignoring not billable EVENT(0, [2012-01-02 01:00:00.000], ON, vmtime::synnefo/vmtime/VM.1, Map(), Christos, synnefo)
- [DEBUG] 2012-02-13 12:54:53,711 main -                 doFullMonthlyBilling(2012-01)     Processing EVENT(3, [2012-01-02 04:00:00.000], 23.0 [MB/Hr], diskspace::pithos/diskspace/DISK.1, Map(), Christos, pithos)
- [DEBUG] 2012-02-13 12:54:53,712 main -                 doFullMonthlyBilling(2012-01)       2 previousResourceEvents
- [DEBUG] 2012-02-13 12:54:53,713 main -                 doFullMonthlyBilling(2012-01)         EVENT(0, [2012-01-02 01:00:00.000], ON, vmtime::synnefo/vmtime/VM.1, Map(), Christos, synnefo)
- [DEBUG] 2012-02-13 12:54:53,714 main -                 doFullMonthlyBilling(2012-01)         EVENT(2, [2012-01-01 03:00:00.000], 99.0 [MB/Hr], diskspace::pithos/diskspace/DISK.1, Map(), Christos, pithos)
- [DEBUG] 2012-02-13 12:54:53,714 main -                 doFullMonthlyBilling(2012-01)       0 theImplicitOFFs
- [DEBUG] 2012-02-13 12:54:53,715 main -                 doFullMonthlyBilling(2012-01)       Processing: ResourceEvent(3,1325469600000,1325469600000,Christos,pithos,diskspace,pithos/diskspace/DISK.1,1.0,23.0,Map())
- [DEBUG] 2012-02-13 12:54:53,716 main -                 doFullMonthlyBilling(2012-01)       Previous  : ResourceEvent(2,1325379600000,1325379600000,Christos,pithos,diskspace,pithos/diskspace/DISK.1,1.0,99.0,Map())
- [DEBUG] 2012-02-13 12:54:53,718 main -                 doFullMonthlyBilling(2012-01)     Processing EVENT(1, [2012-01-02 10:00:00.000], OFF, vmtime::synnefo/vmtime/VM.1, Map(), Christos, synnefo)
- [DEBUG] 2012-02-13 12:54:53,719 main -                 doFullMonthlyBilling(2012-01)       2 previousResourceEvents
- [DEBUG] 2012-02-13 12:54:53,719 main -                 doFullMonthlyBilling(2012-01)         EVENT(0, [2012-01-02 01:00:00.000], ON, vmtime::synnefo/vmtime/VM.1, Map(), Christos, synnefo)
- [DEBUG] 2012-02-13 12:54:53,721 main -                 doFullMonthlyBilling(2012-01)         EVENT(3, [2012-01-02 04:00:00.000], 23.0 [MB/Hr], diskspace::pithos/diskspace/DISK.1, Map(), Christos, pithos)
- [DEBUG] 2012-02-13 12:54:53,721 main -                 doFullMonthlyBilling(2012-01)       0 theImplicitOFFs
- [DEBUG] 2012-02-13 12:54:53,722 main -                 doFullMonthlyBilling(2012-01)       Processing: ResourceEvent(1,1325491200000,1325491200000,Christos,synnefo,vmtime,synnefo/vmtime/VM.1,1.0,0.0,Map())
- [DEBUG] 2012-02-13 12:54:53,735 main -                 doFullMonthlyBilling(2012-01)       Previous  : ResourceEvent(0,1325458800000,1325458800000,Christos,synnefo,vmtime,synnefo/vmtime/VM.1,1.0,1.0,Map())
- [DEBUG] 2012-02-13 12:54:53,736 main -                 doFullMonthlyBilling(2012-01) END
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF() BEGIN
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   +++ [Events by OccurredMillis] +++
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()       EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   --- [Events by OccurredMillis] ---
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2012-01)   BEGIN
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-12)     BEGIN
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-12)       No user state found from cache, will have to (re)compute
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2011-12)       BEGIN
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-11)         BEGIN
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-11)           No user state found from cache, will have to (re)compute
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2011-11)           BEGIN
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10)             BEGIN
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10)               User did not exist before 2011-11-01 00:00:00.000
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10)               Returning INITIAL state [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,0,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,InitialUserStateSetup,0,None,4fa7e12ba0eee3db73fbe8d0)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10)             END
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2011-11)             calculationReason = MonthlyBillingCalculation(2011-11)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2011-11)             Saved [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,1,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillingCalculation(2011-11),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2011-11)             RETURN UserState(true,CKKL,1320098400000,1,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillingCalculation(2011-11),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2011-11)           END
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-11)         END
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2011-12)         calculationReason = MonthlyBillingCalculation(2011-12)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2011-12)         Saved [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,2,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillingCalculation(2011-12),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2011-12)         RETURN UserState(true,CKKL,1320098400000,2,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillingCalculation(2011-12),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2011-12)       END
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-12)     END
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -         walletEntriesForResourceEvent(0)     +++ [EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)] +++
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -         walletEntriesForResourceEvent(0)       Cost policy OnOffCostPolicy for DSLResource(vmtime,Hr,OnOffCostPolicy,true,instanceId)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -         walletEntriesForResourceEvent(0)       PreviousM None
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -         walletEntriesForResourceEvent(0)       Ignoring first event of its kind EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -         walletEntriesForResourceEvent(0)     --- [EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)] ---
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2012-01)     calculationReason = MonthlyBillingCalculation(2012-01)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2012-01)     Saved [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,3,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List(StdResourceEvent(0,1325368800000,1325368800000,CKKL,synnefo,vmtime,VM.1,0.0,1.0,Map()))),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillingCalculation(2012-01),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2012-01)     RETURN UserState(true,CKKL,1320098400000,3,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List(StdResourceEvent(0,1325368800000,1325368800000,CKKL,synnefo,vmtime,VM.1,0.0,1.0,Map()))),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillingCalculation(2012-01),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            doFullMonthlyBilling(2012-01)   END
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   _id = 4fa7e12ba0eee3db73fbe8d0
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   parentId = Some(4fa7e12ba0eee3db73fbe8d0)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   credits = 0.0
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   changeReason = MonthlyBillingCalculation(2012-01)
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   implicitlyIssued [#=0] = List()
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   latestResourceEvents [#=1]:
+DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()     EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)
+DEBUG 17:50:20 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   newWalletEntries [#=0] = List()
  * }}}
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
index 615424f..133b292 100644 (file)
@@ -39,13 +39,15 @@ import gr.grnet.aquarium.store.memory.MemStore
 import gr.grnet.aquarium.util.date.MutableDateCalc
 import gr.grnet.aquarium.logic.accounting.dsl._
 import gr.grnet.aquarium.logic.accounting.{Policy, Accounting}
-import gr.grnet.aquarium.util.{Loggable, ContextualLogger, justForSure}
+import gr.grnet.aquarium.util.{Loggable, ContextualLogger}
 import gr.grnet.aquarium.simulation._
 import gr.grnet.aquarium.uid.{UIDGenerator, ConcurrentVMLocalUIDGenerator}
-import com.ckkloverdos.maybe.{Maybe, Just, NoVal}
+import com.ckkloverdos.maybe.{Maybe, Just}
 import org.junit.{Assert, Ignore, Test}
-import gr.grnet.aquarium.logic.accounting.algorithm.{ExecutableCostPolicyAlgorithm, CostPolicyAlgorithmCompiler, SimpleCostPolicyAlgorithmCompiler}
+import gr.grnet.aquarium.logic.accounting.algorithm.{ExecutableCostPolicyAlgorithm, CostPolicyAlgorithmCompiler}
 import gr.grnet.aquarium.{AquariumException, Configurator}
+import gr.grnet.aquarium.computation.{UserState, BillingMonthInfo, UserStateComputations}
+import gr.grnet.aquarium.computation.reason.MonthlyBillingCalculation
 
 
 /**
@@ -240,7 +242,7 @@ aquariumpolicy:
 
   val UserCKKL  = Aquarium.newUser("CKKL", UserCreationDate)
 
-  val InitialUserState = Computations.createInitialUserState(
+  val InitialUserState = UserState.createInitialUserState(
     userID = UserCKKL.userId,
     userCreationMillis = UserCreationDate.getTime,
     isActive = true,