WIP Resource event handling
[aquarium] / src / main / scala / gr / grnet / aquarium / computation / state / parts / IMStateSnapshot.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.computation
37 package state
38 package parts
39
40 import gr.grnet.aquarium.event.model.im.IMEventModel
41 import gr.grnet.aquarium.util.shortClassNameOf
42 import gr.grnet.aquarium.util.date.MutableDateCalc
43 import gr.grnet.aquarium.computation.parts.RoleHistory
44
45 /**
46  *
47  * @author Christos KK Loverdos <loverdos@gmail.com>
48  */
49
50 case class IMStateSnapshot(
51     /**
52      * This is the latest processed IMEvent
53      */
54     latestIMEvent: IMEventModel,
55
56     /**
57      * The earliest activation time, if it exists.
58      */
59     userEarliestActivationMillis: Option[Long],
60
61     /**
62      * The user creation time, if it exists
63      */
64     userCreationMillis: Option[Long],
65
66     /**
67      * This is the recorded role history
68      */
69     roleHistory: RoleHistory
70 ) {
71
72   /**
73    * True iff the user has ever been activated even once.
74    */
75   def hasBeenActivated: Boolean = {
76     userEarliestActivationMillis.isDefined
77   }
78
79   def hasBeenCreated: Boolean = {
80     userCreationMillis.isDefined
81   }
82
83   /**
84    * Given the newly arrived event, we compute the updated user earliest activation time, if any.
85    * We always update activation time if it is earlier than the currently known activation time.
86    */
87   private[this] def updatedEarliestActivationTime(imEvent: IMEventModel): Option[Long] = {
88     this.userEarliestActivationMillis match {
89       case Some(activationMillis) if imEvent.isStateActive && activationMillis < imEvent.occurredMillis ⇒
90         Some(imEvent.occurredMillis)
91
92       case None if imEvent.isStateActive ⇒
93         Some(imEvent.occurredMillis)
94
95       case other ⇒
96         other
97     }
98   }
99
100   /**
101    * Given the newly arrived event, we compute the updated user creation time, if any.
102    * Only the first `create` event triggers an actual update.
103    */
104   private[this] def updatedCreationTime(imEvent: IMEventModel): Option[Long] = {
105     // Allow only the first `create` event
106     if(this.userCreationMillis.isDefined) {
107       this.userCreationMillis
108     } else if(imEvent.isCreateUser) {
109       Some(imEvent.occurredMillis)
110     } else {
111       None
112     }
113   }
114
115   /**
116    * Given the newly arrived event, we compute the updated role history.
117    */
118   private[this] def updatedRoleHistory(imEvent: IMEventModel): RoleHistory = {
119     this.roleHistory.updatedWithRole(imEvent.role, imEvent.occurredMillis)
120   }
121
122   /**
123    * Computes an updated state and returns a tuple made of four elements:
124    * a) the updated state, b) a `Boolean` indicating whether the user creation
125    * time has changed, c) a `Boolean` indicating whether the user activation
126    * time has changed and d) a `Boolean` indicating whether the user
127    * role history has changed.
128    *
129    * The role history is updated only if the `roleCheck` is not `None` and
130    * the role it represents is different than the role of the `imEvent`.
131    * The motivation for `roleCheck` is to use this method in a loop (as in replaying
132    * events from the [[gr.grnet.aquarium.store.IMEventStore]]).
133    */
134   def updatedWithEvent(imEvent: IMEventModel,
135                        roleCheck: Option[String]): (IMStateSnapshot, Boolean, Boolean, Boolean) = {
136     // Things of interest that may change by the imEvent:
137     // - user creation time
138     // - user activation time
139     // - user role
140
141     val newCreationTime = updatedCreationTime(imEvent)
142     val creationTimeChanged = this.userCreationMillis != newCreationTime
143
144     val newActivationTime = updatedEarliestActivationTime(imEvent)
145     val activationTimeChanged = this.userEarliestActivationMillis != newActivationTime
146
147     val (roleChanged, newRoleHistory) = roleCheck match {
148       case Some(role) if role != imEvent.role ⇒
149         (true, updatedRoleHistory(imEvent))
150
151       case _ ⇒
152         (false, this.roleHistory)
153     }
154
155     val newState = this.copy(
156       latestIMEvent      = imEvent,
157       userCreationMillis = newCreationTime,
158       userEarliestActivationMillis = newActivationTime,
159       roleHistory = newRoleHistory
160     )
161
162     (newState, creationTimeChanged, activationTimeChanged, roleChanged)
163   }
164
165   override def toString = {
166     "%s(\n!! %s\n!! %s\n!! %s\n!! %s)".format(
167       shortClassNameOf(this),
168       latestIMEvent,
169       userCreationMillis.map(new MutableDateCalc(_)),
170       userEarliestActivationMillis.map(new MutableDateCalc(_)),
171       roleHistory
172     )
173   }
174 }
175
176 object IMStateSnapshot {
177   def initial(imEvent: IMEventModel): IMStateSnapshot = {
178     IMStateSnapshot(
179       imEvent,
180       if(imEvent.isStateActive) Some(imEvent.occurredMillis) else None,
181       if(imEvent.isCreateUser) Some(imEvent.occurredMillis) else None,
182       RoleHistory.initial(imEvent.role, imEvent.occurredMillis))
183   }
184 }