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