import gr.grnet.aquarium.actor._
-import akka.config.Supervision.Temporary
import gr.grnet.aquarium.actor.message.event.{ProcessResourceEvent, ProcessIMEvent}
-import gr.grnet.aquarium.actor.message.config.{InitializeUserState, ActorProviderConfigured, AquariumPropertiesLoaded}
+import gr.grnet.aquarium.actor.message.config.{InitializeUserActorState, AquariumPropertiesLoaded}
import gr.grnet.aquarium.util.date.TimeHelpers
+import gr.grnet.aquarium.service.event.BalanceEvent
import gr.grnet.aquarium.event.model.im.IMEventModel
-import gr.grnet.aquarium.actor.message.{GetUserStateResponse, GetUserBalanceResponseData, GetUserBalanceResponse, GetUserStateRequest, GetUserBalanceRequest}
-import gr.grnet.aquarium.util.{LogHelpers, shortClassNameOf, shortNameOfClass, shortNameOfType}
-import gr.grnet.aquarium.computation.reason.{RealtimeBillingCalculation, InitialUserActorSetup, UserStateChangeReason, IMEventArrival, InitialUserStateSetup}
-import gr.grnet.aquarium.{AquariumInternalError, Aquarium}
-import gr.grnet.aquarium.computation.state.parts.IMStateSnapshot
+import message._
+import config.AquariumPropertiesLoaded
+import config.InitializeUserActorState
+import event.ProcessIMEvent
+import event.ProcessResourceEvent
+import gr.grnet.aquarium.util.{LogHelpers, shortClassNameOf}
+import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
import gr.grnet.aquarium.computation.BillingMonthInfo
-import gr.grnet.aquarium.computation.state.{UserStateBootstrap, UserState}
-import gr.grnet.aquarium.event.model.resource.ResourceEventModel
+import gr.grnet.aquarium.charging.state.UserStateBootstrap
+import gr.grnet.aquarium.charging.state.{WorkingAgreementHistory, WorkingUserState, UserStateModel}
+import gr.grnet.aquarium.charging.reason.{InitialUserActorSetup, RealtimeChargingReason}
+import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement}
+import gr.grnet.aquarium.event.model.resource.{StdResourceEvent, ResourceEventModel}
+import message.GetUserBalanceRequest
+import message.GetUserBalanceResponse
+import message.GetUserBalanceResponseData
+import message.GetUserStateRequest
+import message.GetUserStateResponse
+import message.GetUserWalletRequest
+import message.GetUserWalletResponse
+import message.GetUserWalletResponseData
+import scala.Left
+import gr.grnet.aquarium.charging.state.WorkingAgreementHistory
+import scala.Some
+import scala.Right
+import gr.grnet.aquarium.policy.StdUserAgreement
+import gr.grnet.aquarium.charging.state.UserStateBootstrap
+import gr.grnet.aquarium.charging.bill.BillEntry
/**
*
class UserActor extends ReflectiveRoleableActor {
private[this] var _userID: String = "<?>"
- private[this] var _imState: IMStateSnapshot = _
- private[this] var _userState: UserState = _
- private[this] var _latestResourceEventOccurredMillis = TimeHelpers.nowMillis() // some valid datetime
+ private[this] var _workingUserState: WorkingUserState = _
+ private[this] var _userCreationIMEvent: IMEventModel = _
+ private[this] val _workingAgreementHistory: WorkingAgreementHistory = new WorkingAgreementHistory
+ private[this] var _latestIMEventID: String = ""
+ private[this] var _latestResourceEventID: String = ""
+ private[this] var _userStateBootstrap: UserStateBootstrap = _
+
+ def unsafeUserID = {
+ if(!haveUserID) {
+ throw new AquariumInternalError("%s not initialized")
+ }
+
+ this._userID
+ }
- self.lifeCycle = Temporary
+ override def postStop() {
+ DEBUG("I am finally stopped (in postStop())")
+ aquarium.akkaService.notifyUserActorPostStop(this)
+ }
- private[this] def _shutmedown(): Unit = {
- if(haveUserState) {
- UserActorCache.invalidate(_userID)
+ private[this] def shutmedown(): Unit = {
+ if(haveUserID) {
+ aquarium.akkaService.invalidateUserActor(this)
}
-
- self.stop()
}
override protected def onThrowable(t: Throwable, message: AnyRef) = {
LogHelpers.logChainOfCauses(logger, t)
ERROR(t, "Terminating due to: %s(%s)", shortClassNameOf(t), t.getMessage)
- _shutmedown()
+ shutmedown()
}
def role = UserActorRole
- private[this] def aquarium: Aquarium = Aquarium.Instance
+ private[this] def chargingService = aquarium.chargingService
- private[this] def userStateComputations = aquarium.userStateComputations
-
- private[this] def stdUserStateStoreFunc = (userState: UserState) ⇒ {
+ private[this] def stdUserStateStoreFunc = (userState: UserStateModel) ⇒ {
aquarium.userStateStore.insertUserState(userState)
}
- private[this] def _timestampTheshold = {
- aquarium.props.getLong(Aquarium.Keys.user_state_timestamp_threshold).getOr(1000L * 60 * 5 /* 5 minutes */)
+ @inline private[this] def haveUserID = {
+ this._userID ne null
}
- private[this] def haveUserState = {
- this._userState ne null
+ @inline private[this] def haveUserCreationIMEvent = {
+ this._userCreationIMEvent ne null
}
- private[this] def haveIMState = {
- this._imState ne null
+ def onAquariumPropertiesLoaded(event: AquariumPropertiesLoaded): Unit = {
}
- def onAquariumPropertiesLoaded(event: AquariumPropertiesLoaded): Unit = {
+ @inline private[this] def haveAgreements = {
+ this._workingAgreementHistory.size > 0
}
- def onActorProviderConfigured(event: ActorProviderConfigured): Unit = {
+ @inline private[this] def haveWorkingUserState = {
+ this._workingUserState ne null
}
- private[this] def _updateIMStateRoleHistory(imEvent: IMEventModel, roleCheck: Option[String]) = {
- if(haveIMState) {
- val (newState,
- creationTimeChanged,
- activationTimeChanged,
- roleChanged) = this._imState.updatedWithEvent(imEvent, roleCheck)
-
- this._imState = newState
- (creationTimeChanged, activationTimeChanged, roleChanged)
- } else {
- this._imState = IMStateSnapshot.initial(imEvent)
- (
- imEvent.isCreateUser,
- true, // first activation status is a change by default??
- true // first role is a change by default??
+ @inline private[this] def haveUserStateBootstrap = {
+ this._userStateBootstrap ne null
+ }
+
+ private[this] def updateAgreementHistoryFrom(imEvent: IMEventModel): Unit = {
+ if(imEvent.isCreateUser) {
+ if(haveUserCreationIMEvent) {
+ throw new AquariumInternalError(
+ "Got user creation event (id=%s) but I already have one (id=%s)",
+ this._userCreationIMEvent.id,
+ imEvent.id
)
+ }
+
+ this._userCreationIMEvent = imEvent
}
+
+ val effectiveFromMillis = imEvent.occurredMillis
+ val role = imEvent.role
+ // calling unsafe just for the side-effect
+ assert(null ne aquarium.unsafePriceTableForRoleAt(role, effectiveFromMillis))
+
+ val newAgreement = StdUserAgreement(
+ imEvent.id,
+ Some(imEvent.id),
+ effectiveFromMillis,
+ Long.MaxValue,
+ role,
+ PolicyDefinedFullPriceTableRef
+ )
+
+ this._workingAgreementHistory += newAgreement
}
- /**
- * Creates the IMStateSnapshot and returns the number of updates it made to it.
- */
- private[this] def createInitialIMState(): Unit = {
- val store = aquarium.imEventStore
+ private[this] def updateLatestIMEventIDFrom(imEvent: IMEventModel): Unit = {
+ this._latestIMEventID = imEvent.id
+ }
- var _roleCheck = None: Option[String]
+ private[this] def updateLatestResourceEventIDFrom(rcEvent: ResourceEventModel): Unit = {
+ this._latestResourceEventID = rcEvent.id
+ }
- // this._userID is already set up
- store.replayIMEventsInOccurrenceOrder(this._userID) { imEvent ⇒
+ /**
+ * Creates the initial state that is related to IMEvents.
+ */
+ private[this] def initializeStateOfIMEvents(): Unit = {
+ // NOTE: this._userID is already set up by onInitializeUserActorState()
+ aquarium.imEventStore.foreachIMEventInOccurrenceOrder(this._userID) { imEvent ⇒
DEBUG("Replaying %s", imEvent)
- val (creationTimeChanged, activationTimeChanged, roleChanged) = _updateIMStateRoleHistory(imEvent, _roleCheck)
- _roleCheck = this._imState.roleHistory.lastRoleName
-
- DEBUG(
- "(creationTimeChanged, activationTimeChanged, roleChanged)=(%s, %s, %s) using %s",
- creationTimeChanged, activationTimeChanged, roleChanged,
- imEvent
- )
+ updateAgreementHistoryFrom(imEvent)
+ updateLatestIMEventIDFrom(imEvent)
}
- DEBUG("New %s = %s", shortNameOfType[IMStateSnapshot], this._imState)
+ if(haveAgreements) {
+ DEBUG("Initial agreement history %s", this._workingAgreementHistory.toJsonString)
+ logSeparator()
+ }
}
/**
- * Resource events are processed only if the user has been activated.
+ * Resource events are processed only if the user has been created and has agreements.
+ * Otherwise nothing can be computed.
*/
private[this] def shouldProcessResourceEvents: Boolean = {
- haveIMState && this._imState.hasBeenCreated
+ haveUserCreationIMEvent && haveAgreements && haveUserStateBootstrap
}
- private[this] def loadUserStateAndUpdateRoleHistory(): Unit = {
- val userCreationMillis = this._imState.userCreationMillis.get
- val initialRole = this._imState.roleHistory.firstRole.get.name
+ private[this] def loadWorkingUserStateAndUpdateAgreementHistory(): Unit = {
+ assert(this.haveAgreements, "this.haveAgreements")
+ assert(this.haveUserCreationIMEvent, "this.haveUserCreationIMEvent")
- val userStateBootstrap = UserStateBootstrap(
- this._userID,
- userCreationMillis,
- initialRole,
- aquarium.initialAgreementForRole(initialRole, userCreationMillis),
- aquarium.initialBalanceForRole(initialRole, userCreationMillis)
- )
+ val userCreationMillis = this._userCreationIMEvent.occurredMillis
+ val userCreationRole = this._userCreationIMEvent.role // initial role
+ val userCreationIMEventID = this._userCreationIMEvent.id
+
+ if(!haveUserStateBootstrap) {
+ this._userStateBootstrap = UserStateBootstrap(
+ this._userID,
+ userCreationMillis,
+ aquarium.initialUserAgreement(userCreationRole, userCreationMillis, Some(userCreationIMEventID)),
+ aquarium.initialUserBalance(userCreationRole, userCreationMillis)
+ )
+ }
val now = TimeHelpers.nowMillis()
- val userState = userStateComputations.doMonthBillingUpTo(
+ this._workingUserState = chargingService.replayMonthChargingUpTo(
BillingMonthInfo.fromMillis(now),
now,
- userStateBootstrap,
- aquarium.currentResourcesMap,
+ this._userStateBootstrap,
+ aquarium.currentResourceTypesMap,
InitialUserActorSetup(),
- stdUserStateStoreFunc,
- None
+ aquarium.userStateStore.insertUserState
)
- this._userState = userState
-
- // Final touch: Update role history
- if(haveIMState && haveUserState) {
- if(this._userState.roleHistory != this._imState.roleHistory) {
- this._userState = newUserStateWithUpdatedRoleHistory(InitialUserActorSetup())
- }
+ // Final touch: Update agreement history in the working user state.
+ // The assumption is that all agreement changes go via IMEvents, so the
+ // state this._workingAgreementHistory is always the authoritative source.
+ if(haveWorkingUserState) {
+ this._workingUserState.workingAgreementHistory.setFrom(this._workingAgreementHistory)
+ DEBUG("Computed working user state %s", this._workingUserState.toJsonString)
}
}
- private[this] def createInitialUserState(event: InitializeUserState): Unit = {
- if(!haveIMState) {
- // Should have been created from `createIMState()`
- DEBUG("Cannot create user state from %s, since %s = %s", event, shortNameOfClass(classOf[IMStateSnapshot]), this._imState)
+ private[this] def initializeStateOfResourceEvents(event: InitializeUserActorState): Unit = {
+ if(!this.haveAgreements) {
+ DEBUG("Cannot initializeResourceEventsState() from %s. There are no agreements", event)
return
}
- if(!this._imState.hasBeenCreated) {
- // Cannot set the initial state!
- DEBUG("Cannot create %s from %s, since user has not been created", shortNameOfType[UserState], event)
+ if(!this.haveUserCreationIMEvent) {
+ DEBUG("Cannot initializeResourceEventsState() from %s. I never got a CREATE IMEvent", event)
return
}
- // We will also need this functionality when receiving IMEvents,
- // so we place it in a method
- loadUserStateAndUpdateRoleHistory()
+ // We will also need this functionality when receiving IMEvents, so we place it in a method
+ loadWorkingUserStateAndUpdateAgreementHistory()
- if(haveUserState) {
- DEBUG("%s = %s", shortNameOfType[UserState], this._userState)
+ if(haveWorkingUserState) {
+ DEBUG("Initial working user state %s", this._workingUserState.toJsonString)
+ logSeparator()
}
}
- def onInitializeUserState(event: InitializeUserState): Unit = {
+ def onInitializeUserActorState(event: InitializeUserActorState): Unit = {
this._userID = event.userID
DEBUG("Got %s", event)
- createInitialIMState()
- createInitialUserState(event)
- }
-
- /**
- * Creates a new user state, taking into account the latest role history in IM state snapshot.
- * Having an IM state snapshot is a prerequisite, otherwise you get an exception; so check before you
- * call this.
- */
- private[this] def newUserStateWithUpdatedRoleHistory(stateChangeReason: UserStateChangeReason): UserState = {
- // FIXME: Also update agreement
- this._userState.newWithRoleHistory(this._imState.roleHistory, stateChangeReason)
+ initializeStateOfIMEvents()
+ initializeStateOfResourceEvents(event)
}
/**
*/
def onProcessIMEvent(processEvent: ProcessIMEvent): Unit = {
val imEvent = processEvent.imEvent
+ val hadUserCreationIMEvent = haveUserCreationIMEvent
- if(!haveIMState) {
- // This is an error. Should have been initialized from somewhere ...
- throw new AquariumInternalError("Got %s while uninitialized".format(processEvent))
+ if(!haveAgreements) {
+ // This IMEvent has arrived after any ResourceEvents
+ INFO("Arrived after any ResourceEvent: %s", imEvent.toDebugString)
+ initializeStateOfIMEvents()
}
+ else {
+ if(this._latestIMEventID == imEvent.id) {
+ // This happens when the actor is brought to life, then immediately initialized, and then
+ // sent the first IM event. But from the initialization procedure, this IM event will have
+ // already been loaded from DB!
+ INFO("Ignoring first %s", imEvent.toDebugString)
+ logSeparator()
+
+ //this._latestIMEventID = imEvent.id
+ return
+ }
+ if(imEvent.isAddCredits) {
+ if(!hadUserCreationIMEvent && haveUserCreationIMEvent)
+ loadWorkingUserStateAndUpdateAgreementHistory()
+ onHandleAddCreditsEvent(imEvent)
- if(this._imState.latestIMEvent.id == imEvent.id) {
- // This happens when the actor is brought to life, then immediately initialized, and then
- // sent the first IM event. But from the initialization procedure, this IM event will have
- // already been loaded from DB!
- INFO("Ignoring first %s just after %s birth", imEvent.toDebugString, shortClassNameOf(this))
- logSeparator()
-
- return
+ } else {
+ updateAgreementHistoryFrom(imEvent)
+ updateLatestIMEventIDFrom(imEvent)
+ }
}
- val (creationTimeChanged,
- activationTimeChanged,
- roleChanged) = _updateIMStateRoleHistory(imEvent, this._imState.roleHistory.lastRoleName)
-
- DEBUG(
- "(creationTimeChanged, activationTimeChanged, roleChanged)=(%s, %s, %s) using %s",
- creationTimeChanged, activationTimeChanged, roleChanged,
- imEvent
- )
-
// Must also update user state if we know when in history the life of a user begins
- if(creationTimeChanged) {
- if(!haveUserState) {
- loadUserStateAndUpdateRoleHistory()
- INFO("Loaded %s due to %s", shortNameOfType[UserState], imEvent)
- } else {
- // Just update role history
- this._userState = newUserStateWithUpdatedRoleHistory(IMEventArrival(imEvent))
- INFO("Updated %s due to %s", shortNameOfType[UserState], imEvent)
- }
+ if(!hadUserCreationIMEvent && haveUserCreationIMEvent) {
+ INFO("Processing user state, since we had a CREATE IMEvent")
+ loadWorkingUserStateAndUpdateAgreementHistory()
}
- DEBUG("New %s = %s", shortNameOfType[IMStateSnapshot], this._imState)
logSeparator()
}
+ /* Convert astakos message for adding credits
+ to a regular RESOURCE message */
+ def onHandleAddCreditsEvent(imEvent : IMEventModel) = {
+ val credits = imEvent.details(IMEventModel.DetailsNames.credits).toInt.toDouble
+ val event = new StdResourceEvent(
+ imEvent.id,
+ imEvent.occurredMillis,
+ imEvent.receivedMillis,
+ imEvent.userID,
+ imEvent.clientID,
+ imEvent.eventType,
+ imEvent.eventType,
+ credits,
+ imEvent.eventVersion,
+ imEvent.details
+ )
+ //Console.err.println("Event: " + event)
+ //Console.err.println("Total credits before: " + _workingUserState.totalCredits)
+ onProcessResourceEvent(new ProcessResourceEvent(event))
+ //Console.err.println("Total credits after: " + _workingUserState.totalCredits)
+ //Console.err.println("OK.")
+ }
+
def onProcessResourceEvent(event: ProcessResourceEvent): Unit = {
val rcEvent = event.rcEvent
if(!shouldProcessResourceEvents) {
- // This means the user has not been activated. So, we do not process any resource event
+ // This means the user has not been created (at least, as far as Aquarium is concerned).
+ // So, we do not process any resource event
DEBUG("Not processing %s", rcEvent.toJsonString)
logSeparator()
return
}
- this._userState.findLatestResourceEventID match {
- case Some(id) ⇒
- if(id == rcEvent.id) {
- INFO("Ignoring first %s just after %s birth", rcEvent.toDebugString, shortClassNameOf(this))
- logSeparator()
-
- return
- }
+ // Since the latest resource event per resource is recorded in the user state,
+ // we do not need to query the store. Just query the in-memory state.
+ // Note: This is a similar situation with the first IMEvent received right after the user
+ // actor is created.
+ if(this._latestResourceEventID == rcEvent.id) {
+ INFO("Ignoring first %s", rcEvent.toDebugString)
+ logSeparator()
- case _ ⇒
+ return
}
val now = TimeHelpers.nowMillis()
- val dt = now - this._latestResourceEventOccurredMillis
- val belowThreshold = dt <= _timestampTheshold
+ val currentResourcesMap = aquarium.currentResourceTypesMap
+ val chargingReason = RealtimeChargingReason(None, now)
- if(belowThreshold) {
- this._latestResourceEventOccurredMillis = event.rcEvent.occurredMillis
+ val nowBillingMonthInfo = BillingMonthInfo.fromMillis(now)
+ val nowYear = nowBillingMonthInfo.year
+ val nowMonth = nowBillingMonthInfo.month
- DEBUG("Below threshold (%s ms). Not processing %s", this._timestampTheshold, rcEvent.toJsonString)
- return
- }
-
- val userID = this._userID
- val userCreationMillis = this._imState.userCreationMillis.get
- val initialRole = this._imState.roleHistory.firstRoleName.getOrElse(aquarium.defaultInitialUserRole)
- val initialAgreement = aquarium.initialAgreementForRole(initialRole, userCreationMillis)
- val initialCredits = aquarium.initialBalanceForRole(initialRole, userCreationMillis)
- val userStateBootstrap = UserStateBootstrap(
- userID,
- userCreationMillis,
- initialRole,
- initialAgreement,
- initialCredits
- )
- val billingMonthInfo = BillingMonthInfo.fromMillis(now)
- val currentResourcesMap = aquarium.currentResourcesMap
- val calculationReason = RealtimeBillingCalculation(None, now)
val eventOccurredMillis = rcEvent.occurredMillis
+ val eventBillingMonthInfo = BillingMonthInfo.fromMillis(eventOccurredMillis)
+ val eventYear = eventBillingMonthInfo.year
+ val eventMonth = eventBillingMonthInfo.month
+
+ def computeBatch(): Unit = {
+ DEBUG("Going for out of sync charging")
+ this._workingUserState = chargingService.replayMonthChargingUpTo(
+ nowBillingMonthInfo,
+ // Take into account that the event may be out-of-sync.
+ // TODO: Should we use this._latestResourceEventOccurredMillis instead of now?
+ now max eventOccurredMillis,
+ this._userStateBootstrap,
+ currentResourcesMap,
+ chargingReason,
+ stdUserStateStoreFunc
+ )
- DEBUG("Using %s", currentResourcesMap)
+ updateLatestResourceEventIDFrom(rcEvent)
+ }
- this._userState = aquarium.userStateComputations.doMonthBillingUpTo(
- billingMonthInfo,
- now max eventOccurredMillis, // take into account that the event may be out-of-sync
- userStateBootstrap,
- currentResourcesMap,
- calculationReason,
- stdUserStateStoreFunc
- )
+ def computeRealtime(): Unit = {
+ DEBUG("Going for in sync charging")
+ chargingService.processResourceEvent(
+ rcEvent,
+ this._workingUserState,
+ chargingReason,
+ nowBillingMonthInfo,
+ true
+ )
- this._latestResourceEventOccurredMillis = event.rcEvent.occurredMillis
+ updateLatestResourceEventIDFrom(rcEvent)
+ }
+
+ var oldTotalCredits = this._workingUserState.totalCredits
+ // FIXME check these
+ if(nowYear != eventYear || nowMonth != eventMonth) {
+ DEBUG(
+ "nowYear(%s) != eventYear(%s) || nowMonth(%s) != eventMonth(%s)",
+ nowYear, eventYear,
+ nowMonth, eventMonth
+ )
+ computeBatch()
+ }
+ else if(this._workingUserState.latestResourceEventOccurredMillis < rcEvent.occurredMillis) {
+ DEBUG("this._workingUserState.latestResourceEventOccurredMillis < rcEvent.occurredMillis")
+ DEBUG(
+ "%s < %s",
+ TimeHelpers.toYYYYMMDDHHMMSSSSS(this._workingUserState.latestResourceEventOccurredMillis),
+ TimeHelpers.toYYYYMMDDHHMMSSSSS(rcEvent.occurredMillis)
+ )
+ computeRealtime()
+ }
+ else {
+ computeBatch()
+ }
+ var newTotal = this._workingUserState.totalCredits
+ if(oldTotalCredits * newTotal < 0)
+ aquarium.eventBus ! new BalanceEvent(this._workingUserState.userID,
+ newTotal>=0)
+ DEBUG("Updated %s", this._workingUserState)
+ logSeparator()
+ }
- DEBUG("New %s = %s", shortClassNameOf(this._userState), this._userState)
+ def onGetUserBillRequest(event: GetUserBillRequest): Unit = {
+ try{
+ val timeslot = event.timeslot
+ val state= if(haveWorkingUserState) Some(this._workingUserState) else None
+ val billEntry = BillEntry.fromWorkingUserState(timeslot,this._userID,state)
+ val billData = GetUserBillResponseData(this._userID,billEntry)
+ sender ! GetUserBillResponse(Right(billData))
+ } catch {
+ case e:Exception =>
+ e.printStackTrace()
+ sender ! GetUserBillResponse(Left("Internal Server Error [AQU-BILL-0001]"), 500)
+ }
}
def onGetUserBalanceRequest(event: GetUserBalanceRequest): Unit = {
val userID = event.userID
- (haveIMState, haveUserState) match {
+ (haveUserCreationIMEvent, haveWorkingUserState) match {
case (true, true) ⇒
- // (have IMState, have UserState)
- this._imState.hasBeenActivated match {
- case true ⇒
- // (have IMState, activated, have UserState)
- self reply GetUserBalanceResponse(Right(GetUserBalanceResponseData(userID, this._userState.totalCredits)))
-
- case false ⇒
- // (have IMState, not activated, have UserState)
- // Since we have user state, we should have been activated
- self reply GetUserBalanceResponse(Left("Internal Server Error [AQU-BAL-0001]"), 500)
- }
+ // (User CREATEd, with balance state)
+ sender ! GetUserBalanceResponse(Right(GetUserBalanceResponseData(this._userID, this._workingUserState.totalCredits)))
case (true, false) ⇒
- // (have IMState, no UserState)
- this._imState.hasBeenActivated match {
- case true ⇒
- // (have IMState, activated, no UserState)
- // Since we are activated, we should have some state.
- self reply GetUserBalanceResponse(Left("Internal Server Error [AQU-BAL-0002]"), 500)
- case false ⇒
- // (have IMState, not activated, no UserState)
- // The user is virtually unknown
- self reply GetUserBalanceResponse(Left("User %s not activated [AQU-BAL-0003]".format(userID)), 404 /*Not found*/)
- }
+ // (User CREATEd, no balance state)
+ // Return the default initial balance
+ sender ! GetUserBalanceResponse(
+ Right(
+ GetUserBalanceResponseData(
+ this._userID,
+ aquarium.initialUserBalance(this._userCreationIMEvent.role, this._userCreationIMEvent.occurredMillis)
+ )))
case (false, true) ⇒
- // (no IMState, have UserState)
- // A bit ridiculous situation
- self reply GetUserBalanceResponse(Left("Unknown user %s [AQU-BAL-0004]".format(userID)), 404/*Not found*/)
+ // (Not CREATEd, with balance state)
+ // Clearly this is internal error
+ sender ! GetUserBalanceResponse(Left("Internal Server Error [AQU-BAL-0001]"), 500)
case (false, false) ⇒
- // (no IMState, no UserState)
- self reply GetUserBalanceResponse(Left("Unknown user %s [AQU-BAL-0005]".format(userID)), 404/*Not found*/)
+ // (Not CREATEd, no balance state)
+ // The user is completely unknown
+ sender ! GetUserBalanceResponse(Left("Unknown user %s [AQU-BAL-0004]".format(userID)), 404/*Not found*/)
}
}
def onGetUserStateRequest(event: GetUserStateRequest): Unit = {
- haveUserState match {
+ haveWorkingUserState match {
case true ⇒
- self reply GetUserStateResponse(Right(this._userState))
+ sender ! GetUserStateResponse(Right(this._workingUserState))
case false ⇒
- self reply GetUserStateResponse(Left("No state for user %s [AQU-STA-0006]".format(event.userID)))
+ sender ! GetUserStateResponse(Left("No state for user %s [AQU-STA-0006]".format(event.userID)), 404)
+ }
+ }
+
+ def onGetUserWalletRequest(event: GetUserWalletRequest): Unit = {
+ haveWorkingUserState match {
+ case true ⇒
+ DEBUG("haveWorkingUserState: %s", event)
+ sender ! GetUserWalletResponse(
+ Right(
+ GetUserWalletResponseData(
+ this._userID,
+ this._workingUserState.totalCredits,
+ this._workingUserState.walletEntries.toList
+ )))
+
+ case false ⇒
+ DEBUG("!haveWorkingUserState: %s", event)
+ haveUserCreationIMEvent match {
+ case true ⇒
+ DEBUG("haveUserCreationIMEvent: %s", event)
+ sender ! GetUserWalletResponse(
+ Right(
+ GetUserWalletResponseData(
+ this._userID,
+ aquarium.initialUserBalance(this._userCreationIMEvent.role, this._userCreationIMEvent.occurredMillis),
+ Nil
+ )))
+
+ case false ⇒
+ DEBUG("!haveUserCreationIMEvent: %s", event)
+ sender ! GetUserWalletResponse(Left("No wallet for user %s [AQU-WAL-00 8]".format(event.userID)), 404)
+ }
}
}