Reorgaize user state computation for billing period
[aquarium] / src / main / scala / gr / grnet / aquarium / logic / events / ResourceEvent.scala
1 /*
2  * Copyright 2011 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.logic.events
37
38 import net.liftweb.json.{JsonAST, Xml}
39 import gr.grnet.aquarium.util.json.JsonHelpers
40 import gr.grnet.aquarium.logic.accounting.dsl._
41 import com.ckkloverdos.maybe.{MaybeOption, Maybe}
42 import java.util.Date
43 import gr.grnet.aquarium.util.date.MutableDateCalc
44
45 /**
46  * Event sent to Aquarium by clients for resource accounting.
47  * 
48  * @author Christos KK Loverdos <loverdos@gmail.com>.
49  * @author Georgios Gousios <gousiosg@gmail.com>.
50  */
51 case class ResourceEvent(
52     override val id: String,           // The id at the client side (the sender) TODO: Rename to remoteId or something...
53     override val occurredMillis: Long, // When it occurred at client side (the sender)
54     override val receivedMillis: Long, // When it was received by Aquarium
55     userId: String,                    // The user for which this resource is relevant
56     clientId: String,                  // The unique client identifier (usually some hash)
57     resource: String,                  // String representation of the resource type (e.g. "bndup", "vmtime").
58     instanceId: String,                // String representation of the resource instance id
59     eventVersion: String,
60     value: Double,
61     details: ResourceEvent.Details)
62   extends AquariumEvent(id, occurredMillis, receivedMillis) {
63
64   def validate() : Boolean = {
65     !safeResource.isEmpty
66   }
67
68   def safeResource   = if(resource eq null)   "" else resource
69   def safeInstanceId = if(instanceId eq null) "" else instanceId
70
71   def hasResource   = !safeResource.isEmpty
72   def hasInstanceId = !safeInstanceId.isEmpty
73
74   def fullResourceInfo = (safeResource, safeInstanceId)
75
76   def occurredDate = new Date(occurredMillis)
77
78   def occurredDeltaFrom(that: ResourceEvent): Long = {
79     this.occurredMillis - that.occurredMillis
80   }
81
82   def isOccurredWithinMillis(fromMillis: Long, toMillis: Long): Boolean = {
83     require(fromMillis <= toMillis, "fromMillis <= toMillis")
84     fromMillis <= occurredMillis && occurredMillis <= toMillis
85   }
86
87   def isOccurredWithinDates(fromDate: Date, toDate: Date): Boolean = {
88     isOccurredWithinMillis(fromDate.getTime, toDate.getTime)
89   }
90
91   def isReceivedWithinMillis(fromMillis: Long, toMillis: Long): Boolean = {
92     require(fromMillis <= toMillis, "fromMillis <= toMillis")
93     fromMillis <= receivedMillis && receivedMillis <= toMillis
94   }
95   
96   def isReceivedWithinDates(fromDate: Date, toDate: Date): Boolean = {
97     isReceivedWithinMillis(fromDate.getTime, toDate.getTime)
98   }
99
100   def isReceivedWithinDateCalcs(fromDate: MutableDateCalc, toDate: MutableDateCalc): Boolean = {
101     isReceivedWithinMillis(fromDate.getMillis, toDate.getMillis)
102   }
103
104   def isOccurredOrReceivedWithinMillis(fromMillis: Long, toMillis: Long): Boolean = {
105     isOccurredWithinMillis(fromMillis, toMillis) ||
106     isReceivedWithinMillis(fromMillis, toMillis)
107   }
108
109   def isOccurredOrReceivedWithinDates(fromDate: Date, toDate: Date): Boolean = {
110     isOccurredWithinDates(fromDate, toDate) ||
111     isReceivedWithinDates(fromDate, toDate)
112   }
113   
114   def isOutOfSyncForBillingMonth(yearOfBillingMonth: Int, billingMonth: Int) = {
115     val billingStartDateCalc = new MutableDateCalc(yearOfBillingMonth, billingMonth)
116     val billingStartMillis = billingStartDateCalc.toMillis
117     // NOTE: no need to `copy` the mutable `billingStartDateCalc` here because we use it once
118     val billingStopMillis  = billingStartDateCalc.goEndOfThisMonth.toMillis
119
120     isOutOfSyncForBillingPeriod(billingStartMillis, billingStopMillis)
121   }
122   
123   def isOutOfSyncForBillingPeriod(billingStartMillis: Long, billingStopMillis: Long): Boolean = {
124     isReceivedWithinMillis(billingStartMillis, billingStopMillis) &&
125     (occurredMillis < billingStartMillis || occurredMillis > billingStopMillis)
126   }
127
128   def toDebugString(resourcesMap: DSLResourcesMap, useOnlyInstanceId: Boolean = false): String = {
129     val instanceInfo = if(useOnlyInstanceId) instanceId else "%s::%s".format(resource, instanceId)
130     val bvalue = beautifyValue(resourcesMap)
131     val occurredFormatted = new MutableDateCalc(occurredMillis).toYYYYMMDDHHMMSS
132     if(occurredMillis == receivedMillis) {
133       "EVENT(%s, [%s], %s, %s, %s, %s, %s)".format(
134         id,
135         occurredFormatted,
136         bvalue,
137         instanceInfo,
138         details,
139         userId,
140         clientId
141       )
142     } else {
143       "EVENT(%s, [%s], [%s], %s, %s, %s, %s, %s)".format(
144         id,
145         occurredFormatted,
146         new MutableDateCalc(receivedMillis),
147         bvalue,
148         instanceInfo,
149         details,
150         userId,
151         clientId
152       )
153     }
154   }
155
156   private[this]
157   def beatifyValue(resourceProvider: (String) ⇒ Option[DSLResource]): String = {
158     resourceProvider(this.resource) match {
159       case Some(DSLComplexResource(_, _, OnOffCostPolicy, _)) ⇒
160         OnOffPolicyResourceState(this.value).state.toUpperCase
161       case Some(rc @ DSLComplexResource(_, _, _, _)) ⇒
162         "%s [%s]".format(value, rc.unit)
163       case Some(DSLSimpleResource(_, _, OnOffCostPolicy)) ⇒
164         OnOffPolicyResourceState(this.value).state.toUpperCase
165       case Some(rc @ DSLSimpleResource(_, _, _)) ⇒
166         "%s [%s]".format(value, rc.unit)
167       case _ ⇒
168         value.toString
169     }
170   }
171
172   /**
173    * Returns a beautiful string representation of the value.
174    *
175    * @param policy The policy to be asked for resources.
176    * @return A beautiful string representation of the value.
177    */
178   def beautifyValue(policy: DSLPolicy): String = {
179     beatifyValue(policy.findResource)
180   }
181
182   /**
183    * Returns a beautiful string representation of the value.
184    *
185    * @param resourcesMap The resources map to be asked for resources.
186    * @return A beautiful string representation of the value.
187    */
188   def beautifyValue(resourcesMap: DSLResourcesMap): String = {
189     beatifyValue(resourcesMap.findResourceOoooooooooooold)
190   }
191
192   /**
193    * Return `true` iff this is an event regarding a resource with an
194    * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]].
195    */
196   def isOnOffEvent(policy: DSLPolicy): Boolean = {
197     policy.findResource(this.resource).map(_.costPolicy) match {
198       case Some(OnOffCostPolicy) ⇒ true
199       case _ ⇒ false
200     }
201   }
202
203   /**
204    * Return `true` iff this is an event regarding a resource with an
205    * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]] and a
206    * `value` of `"on"`.
207    */
208   def isOnEvent(policy: DSLPolicy): Boolean = {
209     policy.findResource(this.resource) match {
210       case Some(DSLComplexResource(_, _, OnOffCostPolicy, _)) ⇒
211         OnOffPolicyResourceState(this.value).isOn
212       case Some(DSLSimpleResource(_, _, OnOffCostPolicy)) ⇒
213         OnOffPolicyResourceState(this.value).isOn
214       case _ ⇒
215         false
216     }
217   }
218
219   /**
220    * Return `true` iff this is an event regarding a resource with an
221    * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]] and a
222    * `value` of `"off"`.
223    */
224   def isOffEvent(policy: DSLPolicy): Boolean = {
225     policy.findResource(this.resource) match {
226       case Some(DSLComplexResource(_, _, OnOffCostPolicy, _)) ⇒
227         OnOffPolicyResourceState(this.value).isOff
228       case Some(DSLSimpleResource(_, _, OnOffCostPolicy)) ⇒
229         OnOffPolicyResourceState(this.value).isOff
230       case _ ⇒
231         false
232     }
233   }
234
235   def copyWithReceivedMillis(millis: Long) = copy(receivedMillis = millis)
236
237   /**
238    * Find the cost policy of the resource named in this resource event.
239    *
240    * We do not expect cost policies for resources to change, because they are supposed
241    * to be one of their constant characteristics. That is why do not issue a time-dependent
242    * query here for the event's current policy.
243    *
244    * Should the need arises to change the cost policy for a resource, this is a good enough
245    * reason to consider creating another type of resource.
246    */
247   def findCostPolicyM(defaultPolicy: DSLPolicy): Maybe[DSLCostPolicy] = {
248     defaultPolicy.findResource(this.safeResource).map(_.costPolicy): Maybe[DSLCostPolicy]
249   }
250
251   /**
252    * Find the cost policy of the resource named in this resource event.
253    *
254    * We do not expect cost policies for resources to change, because they are supposed
255    * to be one of their constant characteristics. That is why do not issue a time-dependent
256    * query here for the event's current policy.
257    *
258    * Should the need arises to change the cost policy for a resource, this is a good enough
259    * reason to consider creating another type of resource.
260    */
261   def findCostPolicyM(resourcesMap: DSLResourcesMap): Maybe[DSLCostPolicy] = {
262     for {
263       rc <- resourcesMap.findResourceM(this.safeResource)
264     } yield {
265       rc.costPolicy
266     }
267   }
268 }
269
270 object ResourceEvent {
271   type Details = Map[String, String]
272
273   type ResourceType = String
274   type ResourceIdType = String
275   type FullResourceType = (ResourceType, ResourceIdType)
276   type FullResourceTypeMap = Map[FullResourceType, ResourceEvent]
277   type FullMutableResourceTypeMap = scala.collection.mutable.Map[FullResourceType, ResourceEvent]
278
279   def fromJson(json: String): ResourceEvent = {
280     JsonHelpers.jsonToObject[ResourceEvent](json)
281   }
282
283   def fromJValue(jsonAST: JsonAST.JValue): ResourceEvent = {
284     JsonHelpers.jValueToObject[ResourceEvent](jsonAST)
285   }
286
287   def fromBytes(bytes: Array[Byte]): ResourceEvent = {
288     JsonHelpers.jsonBytesToObject[ResourceEvent](bytes)
289   }
290
291   def fromXml(xml: String): ResourceEvent = {
292     fromJValue(Xml.toJson(scala.xml.XML.loadString(xml)))
293   }
294
295   object JsonNames {
296     final val _id = "_id"
297     final val id = "id"
298     final val userId = "userId"
299     final val occurredMillis = "occurredMillis"
300     final val receivedMillis = "receivedMillis"
301     final val clientId = "clientId"
302     final val resource = "resource"
303     final val resourceId = "resourceId"
304     final val eventVersion = "eventVersion"
305     final val value = "value"
306     final val details = "details"
307   }
308 }