Fix NPE with initial user state
[aquarium] / src / main / scala / gr / grnet / aquarium / computation / state / UserStateWorker.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
39 import scala.collection.mutable
40 import gr.grnet.aquarium.util.ContextualLogger
41 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
42 import gr.grnet.aquarium.computation.state.parts.{IgnoredFirstResourceEventsWorker, ImplicitlyIssuedResourceEventsWorker, LatestResourceEventsWorker}
43 import gr.grnet.aquarium.policy.ResourceType
44 import gr.grnet.aquarium.Aquarium
45
46 /**
47  * A helper object holding intermediate state/results during resource event processing.
48  *
49  * @author Christos KK Loverdos <loverdos@gmail.com>
50  */
51 case class UserStateWorker(
52     userID: String,
53
54     /**
55      * This is a collection of all the latest resource events.
56      * We want these in order to correlate incoming resource events with their previous (in `occurredMillis` time)
57      * ones. Will be updated on processing the next resource event.
58      */
59     previousResourceEvents: LatestResourceEventsWorker,
60
61     /**
62      * The implicitly issued resource events at the beginning of the billing period.
63      */
64     implicitlyIssuedStartEvents: ImplicitlyIssuedResourceEventsWorker,
65
66     /**
67      * The resource events that were first (and unused) of their kind.
68      */
69     ignoredFirstResourceEvents: IgnoredFirstResourceEventsWorker,
70     resourceTypesMap: Map[String, ResourceType]
71 ) {
72
73   /**
74    * Finds the previous resource event by checking two possible sources: a) The implicitly terminated resource
75    * events and b) the explicit previous resource events. If the event is found, it is removed from the
76    * respective source.
77    *
78    * If the event is not found, then this must be for a new resource instance.
79    * (and probably then some `zero` resource event must be implied as the previous one)
80    *
81    * @param resource
82    * @param instanceId
83    * @return
84    */
85   def findAndRemovePreviousResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = {
86     // implicitly issued events are checked first
87     implicitlyIssuedStartEvents.findAndRemoveResourceEvent(resource, instanceId) match {
88       case some@Some(_) ⇒
89         some
90       case None ⇒
91         // explicit previous resource events are checked second
92         previousResourceEvents.findAndRemoveResourceEvent(resource, instanceId) match {
93           case some@Some(_) ⇒
94             some
95           case _ ⇒
96             None
97         }
98     }
99   }
100
101   def updateIgnored(resourceEvent: ResourceEventModel): Unit = {
102     ignoredFirstResourceEvents.updateResourceEvent(resourceEvent)
103   }
104
105   def updatePrevious(resourceEvent: ResourceEventModel): Unit = {
106     previousResourceEvents.updateResourceEvent(resourceEvent)
107   }
108
109   def debugTheMaps(clog: ContextualLogger)(rcDebugInfo: ResourceEventModel ⇒ String): Unit = {
110     if(previousResourceEvents.size > 0) {
111       val map = previousResourceEvents.latestEventsMap.map {
112         case (k, v) => (k, rcDebugInfo(v))
113       }
114       clog.debugMap("previousResourceEvents", map, 0)
115     }
116     if(implicitlyIssuedStartEvents.size > 0) {
117       val map = implicitlyIssuedStartEvents.implicitlyIssuedEventsMap.map {
118         case (k, v) => (k, rcDebugInfo(v))
119       }
120       clog.debugMap("implicitlyTerminatedResourceEvents", map, 0)
121     }
122     if(ignoredFirstResourceEvents.size > 0) {
123       val map = ignoredFirstResourceEvents.ignoredFirstEventsMap.map {
124         case (k, v) => (k, rcDebugInfo(v))
125       }
126       clog.debugMap("ignoredFirstResourceEvents", map, 0)
127     }
128   }
129
130   //  private[this]
131   //  def allPreviousAndAllImplicitlyStarted: List[ResourceEvent] = {
132   //    val buffer: FullMutableResourceTypeMap = scala.collection.mutable.Map[FullResourceType, ResourceEvent]()
133   //
134   //    buffer ++= implicitlyIssuedStartEvents.implicitlyIssuedEventsMap
135   //    buffer ++= previousResourceEvents.latestEventsMap
136   //
137   //    buffer.valuesIterator.toList
138   //  }
139
140   /**
141    * Find those events from `implicitlyIssuedStartEvents` and `previousResourceEvents` that will generate implicit
142    * end events along with those implicitly issued events. Before returning, remove the events that generated the
143    * implicit ends from the internal state of this instance.
144    *
145    * @see [[gr.grnet.aquarium.charging.ChargingBehavior]]
146    */
147   def findAndRemoveGeneratorsOfImplicitEndEvents(
148       aquarium: Aquarium,
149       /**
150        * The `occurredMillis` that will be recorded in the synthetic implicit OFFs.
151        * Normally, this will be the end of a billing month.
152        */
153       newOccuredMillis: Long
154   ): (List[ResourceEventModel], List[ResourceEventModel]) = {
155
156     val buffer = mutable.ListBuffer[(ResourceEventModel, ResourceEventModel)]()
157     val checkSet = mutable.Set[ResourceEventModel]()
158
159     def doItFor(map: ResourceEventModel.FullMutableResourceTypeMap): Unit = {
160       val resourceEvents = map.valuesIterator
161       for {
162         resourceEvent ← resourceEvents
163         resourceType ← resourceTypesMap.get(resourceEvent.safeResource)
164         chargingBehavior = aquarium.chargingBehaviorOf(resourceType)
165       } {
166         if(chargingBehavior.supportsImplicitEvents) {
167           if(chargingBehavior.mustConstructImplicitEndEventFor(resourceEvent)) {
168             val implicitEnd = chargingBehavior.constructImplicitEndEventFor(resourceEvent, newOccuredMillis)
169
170             if(!checkSet.contains(resourceEvent)) {
171               checkSet.add(resourceEvent)
172               buffer append ((resourceEvent, implicitEnd))
173             }
174
175             // remove it anyway
176             map.remove((resourceEvent.safeResource, resourceEvent.safeInstanceID))
177           }
178         }
179       }
180     }
181
182     doItFor(previousResourceEvents.latestEventsMap) // we give priority for previous
183     doItFor(implicitlyIssuedStartEvents.implicitlyIssuedEventsMap) // ... over implicitly issued...
184
185     (buffer.view.map(_._1).toList, buffer.view.map(_._2).toList)
186   }
187 }
188
189 object UserStateWorker {
190   def fromUserState(userState: UserState, resourceTypesMap: Map[String, ResourceType]): UserStateWorker = {
191     UserStateWorker(
192       userState.userID,
193       userState.latestResourceEventsSnapshot.toMutableWorker,
194       userState.implicitlyIssuedSnapshot.toMutableWorker,
195       IgnoredFirstResourceEventsWorker.Empty,
196       resourceTypesMap
197     )
198   }
199 }