WIP: Reshaping UserActor
[aquarium] / src / main / scala / gr / grnet / aquarium / actor / service / user / UserActor.scala
1 /*
2  * Copyright 2011-2012 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above
13  *      copyright notice, this list of conditions and the following
14  *      disclaimer in the documentation and/or other materials
15  *      provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  * The views and conclusions contained in the software and
31  * documentation are those of the authors and should not be
32  * interpreted as representing official policies, either expressed
33  * or implied, of GRNET S.A.
34  */
35
36 package gr.grnet.aquarium.actor
37 package service
38 package user
39
40 import com.ckkloverdos.maybe.{Failed, NoVal, Just}
41
42 import gr.grnet.aquarium.actor._
43 import gr.grnet.aquarium.Configurator
44 import gr.grnet.aquarium.user._
45
46 import gr.grnet.aquarium.util.date.TimeHelpers
47 import gr.grnet.aquarium.logic.accounting.RoleAgreements
48 import gr.grnet.aquarium.actor.message.service.router._
49 import message.config.{ActorProviderConfigured, AquariumPropertiesLoaded}
50 import gr.grnet.aquarium.event.im.IMEventModel
51
52
53 /**
54  *
55  * @author Christos KK Loverdos <loverdos@gmail.com>
56  */
57
58 class UserActor extends ReflectiveAquariumActor {
59   private[this] var _userState: UserState = _
60
61   def role = UserActorRole
62
63   private[this] def _configurator: Configurator = Configurator.MasterConfigurator
64 //  private[this] def _userId = _userState.userId
65
66   private[this] def _timestampTheshold =
67     _configurator.props.getLong(Configurator.Keys.user_state_timestamp_threshold).getOr(10000)
68
69   /**
70    * Create an empty state for a user
71    */
72   def createInitialState(userID: String) = {
73     this._userState = DefaultUserStateComputations.createInitialUserState(userID, 0L, true, 0.0)
74   }
75
76
77   /**
78    * Persist current user state
79    */
80   private[this] def saveUserState(): Unit = {
81     _configurator.storeProvider.userStateStore.storeUserState(this._userState) match {
82       case Just(record) => record
83       case NoVal => ERROR("Unknown error saving state")
84       case Failed(e) =>
85         ERROR("Saving state failed: %s".format(e));
86     }
87   }
88
89   def onAquariumPropertiesLoaded(event: AquariumPropertiesLoaded): Unit = {
90   }
91
92   def onActorProviderConfigured(event: ActorProviderConfigured): Unit = {
93   }
94
95   private[this] def processCreateUser(event: IMEventModel): Unit = {
96     val userId = event.userID
97     DEBUG("Creating user from state %s", event)
98     val usersDB = _configurator.storeProvider.userStateStore
99     usersDB.findUserStateByUserId(userId) match {
100       case Just(userState) ⇒
101         WARN("User already created, state = %s".format(userState))
102       case failed@Failed(e) ⇒
103         ERROR("[%s] %s", e.getClass.getName, e.getMessage)
104       case NoVal ⇒
105         val agreement = RoleAgreements.agreementForRole(event.role)
106         DEBUG("User %s assigned agreement %s".format(userId, agreement.name))
107
108         this._userState = DefaultUserStateComputations.createInitialUserState(
109           userId,
110           event.occurredMillis,
111           event.isActive, 0.0, List(event.role), agreement.name)
112         saveUserState
113         DEBUG("Created and stored %s", this._userState)
114     }
115   }
116
117   private[this] def processModifyUser(event: IMEventModel): Unit = {
118     val now = TimeHelpers.nowMillis()
119     val newActive = ActiveStateSnapshot(event.isStateActive, now)
120
121     DEBUG("New active status = %s".format(newActive))
122
123     this._userState = this._userState.copy(activeStateSnapshot = newActive)
124   }
125
126   def onProcessIMEvent(event: ProcessIMEvent): Unit = {
127     val imEvent = event.imEvent
128     if(imEvent.isCreateUser) {
129       processCreateUser(imEvent)
130     } else if(imEvent.isModifyUser) {
131       processModifyUser(imEvent)
132     }
133   }
134
135   def onRequestUserBalance(event: RequestUserBalance): Unit = {
136     val userId = event.userID
137     // FIXME: Implement threshold
138     self reply UserResponseGetBalance(userId, _userState.creditsSnapshot.creditAmount)
139   }
140
141   def onUserRequestGetState(event: UserRequestGetState): Unit = {
142     val userId = event.userID
143    // FIXME: implement
144     self reply UserResponseGetState(userId, this._userState)
145   }
146
147   def onProcessResourceEvent(event: ProcessResourceEvent): Unit = {
148   }
149
150   override def postStop {
151     DEBUG("Actor[%s] stopping, saving state", self.uuid)
152     saveUserState
153   }
154
155   override def preRestart(reason: Throwable) {
156     ERROR(reason, "preRestart: Actor[%s]", self.uuid)
157   }
158
159   override def postRestart(reason: Throwable) {
160     ERROR(reason, "postRestart: Actor[%s]", self.uuid)
161   }
162
163   private[this] def D_userID = {
164     this._userState match {
165       case null ⇒
166         "???"
167
168       case userState ⇒
169         userState.userID
170     }
171   }
172   private[this] def DEBUG(fmt: String, args: Any*) =
173     logger.debug("UserActor[%s]: %s".format(D_userID, fmt.format(args: _*)))
174
175   private[this] def INFO(fmt: String, args: Any*) =
176     logger.info("UserActor[%s]: %s".format(D_userID, fmt.format(args: _*)))
177
178   private[this] def WARN(fmt: String, args: Any*) =
179     logger.warn("UserActor[%s]: %s".format(D_userID, fmt.format(args: _*)))
180
181   private[this] def ERROR(fmt: String, args: Any*) =
182     logger.error("UserActor[%s]: %s".format(D_userID, fmt.format(args: _*)))
183
184   private[this] def ERROR(t: Throwable, fmt: String, args: Any*) =
185       logger.error("UserActor[%s]: %s".format(D_userID, fmt.format(args: _*)), t)
186 }