ID as a suffix is ID and not Id
[aquarium] / src / main / scala / gr / grnet / aquarium / logic / events / ResourceEvent.scala
index a545a1b..e34bb0d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 GRNET S.A. All rights reserved.
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or
  * without modification, are permitted provided that the following
@@ -38,7 +38,10 @@ package gr.grnet.aquarium.logic.events
 import net.liftweb.json.{JsonAST, Xml}
 import gr.grnet.aquarium.util.json.JsonHelpers
 import gr.grnet.aquarium.logic.accounting.dsl._
-import com.ckkloverdos.maybe.Maybe
+import com.ckkloverdos.maybe.{MaybeOption, Maybe}
+import java.util.Date
+import gr.grnet.aquarium.util.date.MutableDateCalc
+import collection.SeqLike
 
 /**
  * Event sent to Aquarium by clients for resource accounting.
@@ -50,10 +53,10 @@ case class ResourceEvent(
     override val id: String,           // The id at the client side (the sender) TODO: Rename to remoteId or something...
     override val occurredMillis: Long, // When it occurred at client side (the sender)
     override val receivedMillis: Long, // When it was received by Aquarium
-    userId: String,                    // The user for which this resource is relevant
-    clientId: String,                  // The unique client identifier (usually some hash)
+    userID: String,                    // The user for which this resource is relevant
+    clientID: String,                  // The unique client identifier (usually some hash)
     resource: String,                  // String representation of the resource type (e.g. "bndup", "vmtime").
-    instanceId: String,                // String representation of the resource instance id
+    instanceID: String,                // String representation of the resource instance id
     eventVersion: String,
     value: Double,
     details: ResourceEvent.Details)
@@ -64,13 +67,94 @@ case class ResourceEvent(
   }
 
   def safeResource   = if(resource eq null)   "" else resource
-  def safeInstanceId = if(instanceId eq null) "" else instanceId
+  def safeInstanceId = if(instanceID eq null) "" else instanceID
 
   def hasResource   = !safeResource.isEmpty
   def hasInstanceId = !safeInstanceId.isEmpty
 
   def fullResourceInfo = (safeResource, safeInstanceId)
 
+  def occurredDate = new Date(occurredMillis)
+
+  def occurredDeltaFrom(that: ResourceEvent): Long = {
+    this.occurredMillis - that.occurredMillis
+  }
+
+  def isOccurredWithinMillis(fromMillis: Long, toMillis: Long): Boolean = {
+    require(fromMillis <= toMillis, "fromMillis <= toMillis")
+    fromMillis <= occurredMillis && occurredMillis <= toMillis
+  }
+
+  def isOccurredWithinDates(fromDate: Date, toDate: Date): Boolean = {
+    isOccurredWithinMillis(fromDate.getTime, toDate.getTime)
+  }
+
+  def isReceivedWithinMillis(fromMillis: Long, toMillis: Long): Boolean = {
+    require(fromMillis <= toMillis, "fromMillis <= toMillis")
+    fromMillis <= receivedMillis && receivedMillis <= toMillis
+  }
+  
+  def isReceivedWithinDates(fromDate: Date, toDate: Date): Boolean = {
+    isReceivedWithinMillis(fromDate.getTime, toDate.getTime)
+  }
+
+  def isReceivedWithinDateCalcs(fromDate: MutableDateCalc, toDate: MutableDateCalc): Boolean = {
+    isReceivedWithinMillis(fromDate.getMillis, toDate.getMillis)
+  }
+
+  def isOccurredOrReceivedWithinMillis(fromMillis: Long, toMillis: Long): Boolean = {
+    isOccurredWithinMillis(fromMillis, toMillis) ||
+    isReceivedWithinMillis(fromMillis, toMillis)
+  }
+
+  def isOccurredOrReceivedWithinDates(fromDate: Date, toDate: Date): Boolean = {
+    isOccurredWithinDates(fromDate, toDate) ||
+    isReceivedWithinDates(fromDate, toDate)
+  }
+  
+  def isOutOfSyncForBillingMonth(yearOfBillingMonth: Int, billingMonth: Int) = {
+    val billingStartDateCalc = new MutableDateCalc(yearOfBillingMonth, billingMonth)
+    val billingStartMillis = billingStartDateCalc.toMillis
+    // NOTE: no need to `copy` the mutable `billingStartDateCalc` here because we use it once
+    val billingStopMillis  = billingStartDateCalc.goEndOfThisMonth.toMillis
+
+    isOutOfSyncForBillingPeriod(billingStartMillis, billingStopMillis)
+  }
+  
+  def isOutOfSyncForBillingPeriod(billingStartMillis: Long, billingStopMillis: Long): Boolean = {
+    isReceivedWithinMillis(billingStartMillis, billingStopMillis) &&
+    (occurredMillis < billingStartMillis || occurredMillis > billingStopMillis)
+  }
+
+  def toDebugString(useOnlyInstanceId: Boolean = false): String = {
+    val instanceInfo = if(useOnlyInstanceId) instanceID else "%s::%s".format(resource, instanceID)
+    val occurredFormatted = new MutableDateCalc(occurredMillis).toYYYYMMDDHHMMSS
+    if(occurredMillis == receivedMillis) {
+      "%sEVENT(%s, [%s], %s, %s, %s, %s, %s)".format(
+        if(isSynthetic) "*" else "",
+        id,
+        occurredFormatted,
+        value,
+        instanceInfo,
+        details,
+        userID,
+        clientID
+      )
+    } else {
+      "%sEVENT(%s, [%s], [%s], %s, %s, %s, %s, %s)".format(
+        if(isSynthetic) "*" else "",
+        id,
+        occurredFormatted,
+        new MutableDateCalc(receivedMillis),
+        value,
+        instanceInfo,
+        details,
+        userID,
+        clientID
+      )
+    }
+  }
+
   /**
    * Return `true` iff this is an event regarding a resource with an
    * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]].
@@ -89,9 +173,7 @@ case class ResourceEvent(
    */
   def isOnEvent(policy: DSLPolicy): Boolean = {
     policy.findResource(this.resource) match {
-      case Some(DSLComplexResource(_, _, OnOffCostPolicy, _)) ⇒
-        OnOffPolicyResourceState(this.value).isOn
-      case Some(DSLSimpleResource(_, _, OnOffCostPolicy)) ⇒
+      case Some(DSLResource(_, _, OnOffCostPolicy, _, _)) ⇒
         OnOffPolicyResourceState(this.value).isOn
       case _ ⇒
         false
@@ -105,9 +187,7 @@ case class ResourceEvent(
    */
   def isOffEvent(policy: DSLPolicy): Boolean = {
     policy.findResource(this.resource) match {
-      case Some(DSLComplexResource(_, _, OnOffCostPolicy, _)) ⇒
-        OnOffPolicyResourceState(this.value).isOff
-      case Some(DSLSimpleResource(_, _, OnOffCostPolicy)) ⇒
+      case Some(DSLResource(_, _, OnOffCostPolicy, _, _)) ⇒
         OnOffPolicyResourceState(this.value).isOff
       case _ ⇒
         false
@@ -126,18 +206,49 @@ case class ResourceEvent(
    * Should the need arises to change the cost policy for a resource, this is a good enough
    * reason to consider creating another type of resource.
    */
-  def findCostPolicy(defaultPolicy: DSLPolicy): Maybe[DSLCostPolicy] = {
+  def findCostPolicyM(defaultPolicy: DSLPolicy): Maybe[DSLCostPolicy] = {
     defaultPolicy.findResource(this.safeResource).map(_.costPolicy): Maybe[DSLCostPolicy]
   }
+
+  /**
+   * Find the cost policy of the resource named in this resource event.
+   *
+   * We do not expect cost policies for resources to change, because they are supposed
+   * to be one of their constant characteristics. That is why do not issue a time-dependent
+   * query here for the event's current policy.
+   *
+   * Should the need arises to change the cost policy for a resource, this is a good enough
+   * reason to consider creating another type of resource.
+   */
+  def findCostPolicyM(resourcesMap: DSLResourcesMap): Maybe[DSLCostPolicy] = {
+    for {
+      rc <- resourcesMap.findResource(this.safeResource)
+    } yield {
+      rc.costPolicy
+    }
+  }
+
+  /**
+   * `Synthetic` means that Aquarium has manufactured this resource event for some purpose. For example, the implicitly
+   * issued resource events at the end a a billing period.
+   *
+   * @return `true` iff this resource event is synthetic.
+   */
+  def isSynthetic = {
+    details contains ResourceEvent.JsonNames.details_aquarium_is_synthetic
+  }
 }
 
 object ResourceEvent {
   type Details = Map[String, String]
+  final val EmptyDetails: Details = Map()
 
   type ResourceType = String
   type ResourceIdType = String
   type FullResourceType = (ResourceType, ResourceIdType)
-  
+  type FullResourceTypeMap = Map[FullResourceType, ResourceEvent]
+  type FullMutableResourceTypeMap = scala.collection.mutable.Map[FullResourceType, ResourceEvent]
+
   def fromJson(json: String): ResourceEvent = {
     JsonHelpers.jsonToObject[ResourceEvent](json)
   }
@@ -154,11 +265,24 @@ object ResourceEvent {
     fromJValue(Xml.toJson(scala.xml.XML.loadString(xml)))
   }
 
+  def setAquariumSynthetic(map: ResourceEvent.Details): ResourceEvent.Details = {
+    map.updated(JsonNames.details_aquarium_is_synthetic, "true")
+  }
+
+  def setAquariumSyntheticAndImplicitEnd(map: ResourceEvent.Details): ResourceEvent.Details = {
+    map.
+      updated(JsonNames.details_aquarium_is_synthetic, "true").
+      updated(JsonNames.details_aquarium_is_implicit_end, "true")
+  }
+  
+  def sortByOccurred[S <: Seq[ResourceEvent]](events: S with SeqLike[ResourceEvent, S]): S = {
+    events.sortWith(_.occurredMillis <= _.occurredMillis)
+  }
+
   object JsonNames {
     final val _id = "_id"
     final val id = "id"
     final val userId = "userId"
-    //final val timestamp = "timestamp" // TODO: deprecate in favor of "occurredMillis"
     final val occurredMillis = "occurredMillis"
     final val receivedMillis = "receivedMillis"
     final val clientId = "clientId"
@@ -168,8 +292,11 @@ object ResourceEvent {
     final val value = "value"
     final val details = "details"
 
-    // ResourceType: VMTime
-    final val vmId = "vmId"
-    final val action = "action" // "on", "off"
+    // This is set in the details map to indicate a synthetic resource event (ie not a real one).
+    // Examples of synthetic resource events are those that are implicitly generated at the
+    // end of the billing period (e.g. `OFF`s).
+    final val details_aquarium_is_synthetic    = "__aquarium_is_synthetic__"
+
+    final val details_aquarium_is_implicit_end = "__aquarium_is_implicit_end__"
   }
 }
\ No newline at end of file