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