Factoring out common code
[aquarium] / src / main / scala / gr / grnet / aquarium / logic / events / ResourceEvent.scala
index 755ae83..de7e9b6 100644 (file)
 
 package gr.grnet.aquarium.logic.events
 
-import gr.grnet.aquarium.logic.accounting.Policy
-import net.liftweb.json.{Extraction, parse => parseJson, JsonAST, Xml}
+import net.liftweb.json.{JsonAST, Xml}
 import gr.grnet.aquarium.util.json.JsonHelpers
-import gr.grnet.aquarium.MasterConf._
-import com.ckkloverdos.maybe.{Failed, NoVal, Just}
+import gr.grnet.aquarium.logic.accounting.dsl._
+import com.ckkloverdos.maybe.{MaybeOption, Maybe}
+import java.util.Date
+import gr.grnet.aquarium.util.date.DateCalculator
 
 /**
  * Event sent to Aquarium by clients for resource accounting.
@@ -48,68 +49,233 @@ import com.ckkloverdos.maybe.{Failed, NoVal, Just}
  * @author Georgios Gousios <gousiosg@gmail.com>.
  */
 case class ResourceEvent(
-  override val id: String,
-  userId: String,
-  clientId: String,
-  resource: String,
-  override val timestamp: Long,
-  eventVersion: String,
-  details: Map[String, String]
-) extends AquariumEvent(id, timestamp) {
+    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)
+    resource: String,                  // String representation of the resource type (e.g. "bndup", "vmtime").
+    instanceId: String,                // String representation of the resource instance id
+    eventVersion: String,
+    value: Double,
+    details: ResourceEvent.Details)
+  extends AquariumEvent(id, occurredMillis, receivedMillis) {
 
   def validate() : Boolean = {
+    !safeResource.isEmpty
+  }
+
+  def safeResource   = if(resource eq null)   "" else resource
+  def safeInstanceId = if(instanceId eq null) "" else instanceId
+
+  def hasResource   = !safeResource.isEmpty
+  def hasInstanceId = !safeInstanceId.isEmpty
+
+  def fullResourceInfo = (safeResource, safeInstanceId)
+
+  def isOccurredWithinMillis(fromMillis: Long, toMillis: Long): Boolean = {
+    fromMillis <= occurredMillis && occurredMillis <= toMillis
+  }
+
+  def isOccurredWithinDates(fromDate: Date, toDate: Date): Boolean = {
+    fromDate.getTime <= occurredMillis && occurredMillis <= toDate.getTime
+  }
+
+  def isReceivedWithinMillis(fromMillis: Long, toMillis: Long): Boolean = {
+    fromMillis <= receivedMillis && receivedMillis <= toMillis
+  }
+  
+  def isReceivedWithinDates(fromDate: Date, toDate: Date): Boolean = {
+    fromDate.getTime <= receivedMillis && receivedMillis <= toDate.getTime
+  }
+
+  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 toDebugString(resourcesMap: DSLResourcesMap, useOnlyInstanceId: Boolean): String = {
+    val instanceInfo = if(useOnlyInstanceId) instanceId else "%s::%s".format(resource, instanceId)
+    val bvalue = beautifyValue(resourcesMap)
+    val occurredFormatted = new DateCalculator(occurredMillis).toString
+    if(occurredMillis == receivedMillis) {
+      "EVENT(%s, [%s], %s, %s, %s, %s, %s)".format(
+        id,
+        occurredFormatted,
+        instanceInfo,
+        bvalue,
+        details,
+        userId,
+        clientId
+      )
+    } else {
+      "EVENT(%s, [%s], [%s], %s, %s, %s, %s, %s)".format(
+        id,
+        occurredFormatted,
+        new DateCalculator(receivedMillis),
+        instanceInfo,
+        bvalue,
+        details,
+        userId,
+        clientId
+      )
+    }
+  }
 
-    val userState = MasterConf.userStore.findUserStateByUserId(userId) match {
-      case Just(x) => x
-      case NoVal =>
-          logger.warn("Inexistent user for resource event: %s".format(this.toJson))
-          return false
-      case Failed(x,y) =>
-        logger.warn("Error retrieving user state: %s".format(x))
-        return false
+  private[this]
+  def beatifyValue(resourceProvider: (String) ⇒ Option[DSLResource]): String = {
+    resourceProvider(this.resource) match {
+      case Some(DSLComplexResource(_, _, OnOffCostPolicy, _)) ⇒
+        OnOffPolicyResourceState(this.value).state.toUpperCase
+      case Some(rc @ DSLComplexResource(_, _, _, _)) ⇒
+        "%s [%s]".format(value, rc.unit)
+      case Some(DSLSimpleResource(_, _, OnOffCostPolicy)) ⇒
+        OnOffPolicyResourceState(this.value).state.toUpperCase
+      case Some(rc @ DSLSimpleResource(_, _, _)) ⇒
+        "%s [%s]".format(value, rc.unit)
+      case _ ⇒
+        value.toString
     }
+  }
+
+  /**
+   * Returns a beautiful string representation of the value.
+   *
+   * @param policy The policy to be asked for resources.
+   * @return A beautiful string representation of the value.
+   */
+  def beautifyValue(policy: DSLPolicy): String = {
+    beatifyValue(policy.findResource)
+  }
+
+  /**
+   * Returns a beautiful string representation of the value.
+   *
+   * @param resourcesMap The resources map to be asked for resources.
+   * @return A beautiful string representation of the value.
+   */
+  def beautifyValue(resourcesMap: DSLResourcesMap): String = {
+    beatifyValue(resourcesMap.findResource)
+  }
 
-    //TODO: Get agreement for ev.userid
-    val agreement = userState.agreement.data.name
+  /**
+   * Return `true` iff this is an event regarding a resource with an
+   * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]].
+   */
+  def isOnOffEvent(policy: DSLPolicy): Boolean = {
+    policy.findResource(this.resource).map(_.costPolicy) match {
+      case Some(OnOffCostPolicy) ⇒ true
+      case _ ⇒ false
+    }
+  }
 
-    val agr = Policy.policy.findAgreement(agreement) match {
-      case Some(x) => x
-      case None => return false
+  /**
+   * Return `true` iff this is an event regarding a resource with an
+   * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]] and a
+   * `value` of `"on"`.
+   */
+  def isOnEvent(policy: DSLPolicy): Boolean = {
+    policy.findResource(this.resource) match {
+      case Some(DSLComplexResource(_, _, OnOffCostPolicy, _)) ⇒
+        OnOffPolicyResourceState(this.value).isOn
+      case Some(DSLSimpleResource(_, _, OnOffCostPolicy)) ⇒
+        OnOffPolicyResourceState(this.value).isOn
+      case _ ⇒
+        false
     }
+  }
 
-    val res = Policy.policy.findResource(resource) match {
-      case Some(x) => x
-      case None => return false
+  /**
+   * Return `true` iff this is an event regarding a resource with an
+   * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]] and a
+   * `value` of `"off"`.
+   */
+  def isOffEvent(policy: DSLPolicy): Boolean = {
+    policy.findResource(this.resource) match {
+      case Some(DSLComplexResource(_, _, OnOffCostPolicy, _)) ⇒
+        OnOffPolicyResourceState(this.value).isOff
+      case Some(DSLSimpleResource(_, _, OnOffCostPolicy)) ⇒
+        OnOffPolicyResourceState(this.value).isOff
+      case _ ⇒
+        false
     }
+  }
 
-    if (!details.keySet.contains("value"))
-      return false
+  def copyWithReceivedMillis(millis: Long) = copy(receivedMillis = millis)
 
-    //if (resource.complex && !details.keySet.contains("instance-id"))
-    //  return false
-    //TODO: See how to describe complex resources
+  /**
+   * 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(defaultPolicy: DSLPolicy): Maybe[DSLCostPolicy] = {
+    defaultPolicy.findResource(this.safeResource).map(_.costPolicy): Maybe[DSLCostPolicy]
+  }
 
-    true
+  /**
+   * 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 findCostPolicy(resourcesMap: DSLResourcesMap): Option[DSLCostPolicy] = {
+    resourcesMap.findResource(this.safeResource).map(_.costPolicy)
   }
 }
 
 object ResourceEvent {
+  type Details = Map[String, String]
+
+  type ResourceType = String
+  type ResourceIdType = String
+  type FullResourceType = (ResourceType, ResourceIdType)
+  
   def fromJson(json: String): ResourceEvent = {
-    implicit val formats = JsonHelpers.DefaultJsonFormats
-    val jsonAST = parseJson(json)
-    Extraction.extract[ResourceEvent](jsonAST)
+    JsonHelpers.jsonToObject[ResourceEvent](json)
   }
 
   def fromJValue(jsonAST: JsonAST.JValue): ResourceEvent = {
-    implicit val formats = JsonHelpers.DefaultJsonFormats
-    Extraction.extract(jsonAST)
+    JsonHelpers.jValueToObject[ResourceEvent](jsonAST)
   }
 
   def fromBytes(bytes: Array[Byte]): ResourceEvent = {
-    fromJson(new String(bytes, "UTF-8"))
+    JsonHelpers.jsonBytesToObject[ResourceEvent](bytes)
   }
 
   def fromXml(xml: String): ResourceEvent = {
     fromJValue(Xml.toJson(scala.xml.XML.loadString(xml)))
   }
+
+  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"
+    final val resource = "resource"
+    final val resourceId = "resourceId"
+    final val eventVersion = "eventVersion"
+    final val value = "value"
+    final val details = "details"
+
+    // ResourceType: VMTime
+    final val vmId = "vmId"
+    final val action = "action" // "on", "off"
+  }
 }
\ No newline at end of file