756ea58ab2fbfed812d6d226737d730617494a70
[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(DSLResource(_, _, OnOffCostPolicy, _, _)) ⇒
160         OnOffPolicyResourceState(this.value).state.toUpperCase
161       case Some(rc @ DSLResource(_, _, _, _, _)) ⇒
162         "%s [%s]".format(value, rc.unit)
163       case _ ⇒
164         value.toString
165     }
166   }
167
168   /**
169    * Returns a beautiful string representation of the value.
170    *
171    * @param policy The policy to be asked for resources.
172    * @return A beautiful string representation of the value.
173    */
174   def beautifyValue(policy: DSLPolicy): String = {
175     beatifyValue(policy.findResource)
176   }
177
178   /**
179    * Returns a beautiful string representation of the value.
180    *
181    * @param resourcesMap The resources map to be asked for resources.
182    * @return A beautiful string representation of the value.
183    */
184   def beautifyValue(resourcesMap: DSLResourcesMap): String = {
185     beatifyValue(resourcesMap.findResource)
186   }
187
188   /**
189    * Return `true` iff this is an event regarding a resource with an
190    * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]].
191    */
192   def isOnOffEvent(policy: DSLPolicy): Boolean = {
193     policy.findResource(this.resource).map(_.costPolicy) match {
194       case Some(OnOffCostPolicy) ⇒ true
195       case _ ⇒ false
196     }
197   }
198
199   /**
200    * Return `true` iff this is an event regarding a resource with an
201    * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]] and a
202    * `value` of `"on"`.
203    */
204   def isOnEvent(policy: DSLPolicy): Boolean = {
205     policy.findResource(this.resource) match {
206       case Some(DSLResource(_, _, OnOffCostPolicy, _, _)) ⇒
207         OnOffPolicyResourceState(this.value).isOn
208       case _ ⇒
209         false
210     }
211   }
212
213   /**
214    * Return `true` iff this is an event regarding a resource with an
215    * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]] and a
216    * `value` of `"off"`.
217    */
218   def isOffEvent(policy: DSLPolicy): Boolean = {
219     policy.findResource(this.resource) match {
220       case Some(DSLResource(_, _, OnOffCostPolicy, _, _)) ⇒
221         OnOffPolicyResourceState(this.value).isOff
222       case _ ⇒
223         false
224     }
225   }
226
227   def copyWithReceivedMillis(millis: Long) = copy(receivedMillis = millis)
228
229   /**
230    * Find the cost policy of the resource named in this resource event.
231    *
232    * We do not expect cost policies for resources to change, because they are supposed
233    * to be one of their constant characteristics. That is why do not issue a time-dependent
234    * query here for the event's current policy.
235    *
236    * Should the need arises to change the cost policy for a resource, this is a good enough
237    * reason to consider creating another type of resource.
238    */
239   def findCostPolicyM(defaultPolicy: DSLPolicy): Maybe[DSLCostPolicy] = {
240     defaultPolicy.findResource(this.safeResource).map(_.costPolicy): Maybe[DSLCostPolicy]
241   }
242
243   /**
244    * Find the cost policy of the resource named in this resource event.
245    *
246    * We do not expect cost policies for resources to change, because they are supposed
247    * to be one of their constant characteristics. That is why do not issue a time-dependent
248    * query here for the event's current policy.
249    *
250    * Should the need arises to change the cost policy for a resource, this is a good enough
251    * reason to consider creating another type of resource.
252    */
253   def findCostPolicyM(resourcesMap: DSLResourcesMap): Maybe[DSLCostPolicy] = {
254     for {
255       rc <- resourcesMap.findResource(this.safeResource)
256     } yield {
257       rc.costPolicy
258     }
259   }
260 }
261
262 object ResourceEvent {
263   type Details = Map[String, String]
264   final val EmptyDetails: Details = Map()
265
266   type ResourceType = String
267   type ResourceIdType = String
268   type FullResourceType = (ResourceType, ResourceIdType)
269 //  type FullResourceTypeMap = Map[FullResourceType, ResourceEvent]
270   type FullMutableResourceTypeMap = scala.collection.mutable.Map[FullResourceType, ResourceEvent]
271
272   def fromJson(json: String): ResourceEvent = {
273     JsonHelpers.jsonToObject[ResourceEvent](json)
274   }
275
276   def fromJValue(jsonAST: JsonAST.JValue): ResourceEvent = {
277     JsonHelpers.jValueToObject[ResourceEvent](jsonAST)
278   }
279
280   def fromBytes(bytes: Array[Byte]): ResourceEvent = {
281     JsonHelpers.jsonBytesToObject[ResourceEvent](bytes)
282   }
283
284   def fromXml(xml: String): ResourceEvent = {
285     fromJValue(Xml.toJson(scala.xml.XML.loadString(xml)))
286   }
287
288   object JsonNames {
289     final val _id = "_id"
290     final val id = "id"
291     final val userId = "userId"
292     final val occurredMillis = "occurredMillis"
293     final val receivedMillis = "receivedMillis"
294     final val clientId = "clientId"
295     final val resource = "resource"
296     final val resourceId = "resourceId"
297     final val eventVersion = "eventVersion"
298     final val value = "value"
299     final val details = "details"
300   }
301 }