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