Refactored BillEntry and relevant classes.
[aquarium] / src / main / scala / gr / grnet / aquarium / charging / bill / BillEntry.scala
index 5de5905..a449e13 100644 (file)
@@ -5,19 +5,23 @@ import gr.grnet.aquarium.util.json.JsonSupport
 import com.ckkloverdos.resource.FileStreamResource
 import java.io.File
 import com.ckkloverdos.props.Props
-import gr.grnet.aquarium.converter.{PrettyJsonTextFormat, StdConverters}
+import gr.grnet.aquarium.converter.{CompactJsonTextFormat, PrettyJsonTextFormat, StdConverters}
 import gr.grnet.aquarium.{Aquarium, ResourceLocator, AquariumBuilder}
 import gr.grnet.aquarium.store.memory.MemStoreProvider
 import gr.grnet.aquarium.converter.StdConverters._
-import scala.Some
+import scala._
 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
 import java.util.concurrent.atomic.AtomicLong
 import java.util.{Date, Calendar, GregorianCalendar}
 import gr.grnet.aquarium.charging.wallet.WalletEntry
-import collection.parallel.mutable
-import collection.mutable.ListBuffer
+import scala.collection.parallel.mutable
+import scala.collection.mutable.ListBuffer
 import gr.grnet.aquarium.Aquarium.EnvKeys
 import gr.grnet.aquarium.charging.Chargeslot
+import scala.collection.immutable.TreeMap
+import scala.Some
+import gr.grnet.aquarium.charging.Chargeslot
+
 
 /*
 * Copyright 2011-2012 GRNET S.A. All rights reserved.
@@ -59,23 +63,30 @@ import gr.grnet.aquarium.charging.Chargeslot
 * @author Prodromos Gerakios <pgerakios@grnet.gr>
 */
 
-class EventEntry(val id:String,
-                 val unitPrice:String,
-                 val startTime:String,
-                 val endTime:String,
-                 val ellapsedTime:String,
-                 val credits:String) extends JsonSupport {
+case class ChargeEntry(val id:String,
+                       val unitPrice:String,
+                       val startTime:String,
+                       val endTime:String,
+                       val ellapsedTime:String,
+                       val credits:String)
+  extends JsonSupport {}
+
+
+class EventEntry(val eventType : String,
+                 val details   : List[ChargeEntry])
+ extends JsonSupport {}
 
-}
 
-class ResourceEntry(val resourceName : String,
-                    val resourceType : String,
-                    val unitName : String,
-                    val totalCredits : String,
-                    val eventType:String,
-                    val details : List[EventEntry]) extends JsonSupport {
+case class ResourceEntry(val resourceName : String,
+                         val resourceType : String,
+                         val unitName : String,
+                         val totalCredits : String,
+                         val details : List[EventEntry])
+extends JsonSupport {}
 
-}
+
+abstract class AbstractBillEntry
+ extends JsonSupport {}
 
 class BillEntry(val id:String,
                 val userID : String,
@@ -85,11 +96,11 @@ class BillEntry(val id:String,
                 val startTime:String,
                 val endTime:String,
                 val bill:List[ResourceEntry]
-              )  extends JsonSupport {
+              )
+ extends AbstractBillEntry {}
 
-}
 
-object BillEntry {
+object AbstractBillEntry {
 
   private[this] val counter = new AtomicLong(0L)
   private[this] def nextUIDObject() = counter.getAndIncrement
@@ -104,21 +115,28 @@ object BillEntry {
    Timeslot(dstart,dend)
   } */
 
-  private[this] def toEventEntry(c:Chargeslot) : EventEntry = {
+  private[this] def toChargeEntry(c:Chargeslot) : ChargeEntry = {
     val unitPrice = c.unitPrice.toString
     val startTime = c.startMillis.toString
     val endTime   = c.stopMillis.toString
     val difTime   = (c.stopMillis - c.startMillis).toString
     val credits   = c.creditsToSubtract.toString
-    new EventEntry(counter.getAndIncrement.toString,unitPrice,
+    new ChargeEntry(counter.getAndIncrement.toString,unitPrice,
                     startTime,endTime,difTime,credits)
-
   }
 
+  private[this] def toEventEntry(eventType:String,c:Chargeslot) : EventEntry =
+    new EventEntry(eventType,List(toChargeEntry(c)))
+
 
   private[this] def toResourceEntry(w:WalletEntry) : ResourceEntry = {
     assert(w.sumOfCreditsToSubtract==0.0 || w.chargslotCount > 0)
-    val rcName =  w.resource.toString
+    val rcName = w.currentResourceEvent.clientID match {
+            case "pithos" =>
+              w.currentResourceEvent.details("path")
+            case _ =>
+              w.currentResourceEvent.instanceID
+        }
     val rcType =  w.resourceType.name
     val rcUnitName = w.resourceType.unit
     val eventEntry = new ListBuffer[EventEntry]
@@ -147,19 +165,20 @@ object BillEntry {
     for { c <- w.chargeslots }{
       if(c.creditsToSubtract != 0.0) {
         //Console.err.println("c.creditsToSubtract : " + c.creditsToSubtract)
-        eventEntry += toEventEntry(c)
+        eventEntry += toEventEntry(eventType.toString,c)
         //credits += c.creditsToSubtract
       }
     }
     //Console.err.println("TOTAL resource event credits: " + credits)
-    new ResourceEntry(rcName,rcType,rcUnitName,credits.toString,eventType.toString,eventEntry.toList)
+    new ResourceEntry(rcName,rcType,rcUnitName,credits.toString,eventEntry.toList)
   }
 
   private[this] def resourceEntriesAt(t:Timeslot,w:WorkingUserState) : (List[ResourceEntry],Double) = {
     val ret = new ListBuffer[ResourceEntry]
     var sum = 0.0
     //Console.err.println("Wallet entries: " + w.walletEntries)
-    for { i <- w.walletEntries} {
+    val walletEntries = w.walletEntries
+    for { i <- walletEntries} {
       if(t.contains(i.referenceTimeslot) && i.sumOfCreditsToSubtract != 0.0){
         //Console.err.println("i.sumOfCreditsToSubtract : " + i.sumOfCreditsToSubtract)
         sum += i.sumOfCreditsToSubtract
@@ -172,7 +191,22 @@ object BillEntry {
     (ret.toList,sum)
   }
 
-  def fromWorkingUserState(t:Timeslot,userID:String,w:Option[WorkingUserState]) : BillEntry = {
+  private[this] def addResourceEntries(a:ResourceEntry,b:ResourceEntry) : ResourceEntry = {
+    assert(a.resourceName == b.resourceName)
+    val totalCredits = (a.totalCredits.toDouble+b.totalCredits.toDouble).toString
+    a.copy(a.resourceName,a.resourceType,a.unitName,totalCredits,a.details ::: b.details)
+  }
+
+  private[this] def aggregateResourceEntries(re:List[ResourceEntry]) : List[ResourceEntry] =
+    re.foldLeft(TreeMap[String,ResourceEntry]()){ (map,r1) =>
+      map.get(r1.resourceName) match {
+        case None => map + ((r1.resourceName,r1))
+        case Some(r0) => (map - r0.resourceName) +
+                         ((r0.resourceName, addResourceEntries(r0,r1)))
+      }
+    }.values.toList
+
+  def fromWorkingUserState(t:Timeslot,userID:String,w:Option[WorkingUserState]) : AbstractBillEntry = {
     val ret = w match {
       case None =>
           new BillEntry(counter.getAndIncrement.toString,
@@ -183,19 +217,39 @@ object BillEntry {
                         Nil)
       case Some(w) =>
         val (rcEntries,rcEntriesCredits) = resourceEntriesAt(t,w)
+        val resMap = aggregateResourceEntries(rcEntries)
         new BillEntry(counter.getAndIncrement.toString,
                       userID,"ok",
                       w.totalCredits.toString,
                       rcEntriesCredits.toString,
                       t.from.getTime.toString,t.to.getTime.toString,
-                      rcEntries)
+                      resMap
+                     )
     }
     //Console.err.println("JSON: " +  ret.toJsonString)
     ret
   }
 
-  //
+  val jsonSample = "{\n  \"id\":\"2\",\n  \"userID\":\"loverdos@grnet.gr\",\n  \"status\":\"ok\",\n  \"remainingCredits\":\"3130.0000027777783\",\n  \"deductedCredits\":\"5739.9999944444435\",\n  \"startTime\":\"1341090000000\",\n  \"endTime\":\"1343768399999\",\n  \"bill\":[{\n    \"resourceName\":\"diskspace\",\n    \"resourceType\":\"diskspace\",\n    \"unitName\":\"MB/Hr\",\n    \"totalCredits\":\"2869.9999972222217\",\n    \"eventType\":\"object update@/Papers/GOTO_HARMFUL.PDF\",\n\t    \"details\":[\n\t     {\"totalCredits\":\"2869.9999972222217\",\n\t      \"details\":[{\n\t      \"id\":\"0\",\n\t      \"unitPrice\":\"0.01\",\n\t      \"startTime\":\"1342735200000\",\n\t      \"endTime\":\"1343768399999\",\n\t      \"ellapsedTime\":\"1033199999\",\n\t      \"credits\":\"2869.9999972222217\"\n\t    \t}]\n\t    }\n\t  ]\n  },{\n    \"resourceName\":\"diskspace\",\n    \"resourceType\":\"diskspace\",\n    \"unitName\":\"MB/Hr\",\n    \"totalCredits\":\"2869.9999972222217\",\n    \"eventType\":\"object update@/Papers/GOTO_HARMFUL.PDF\",\n    \"details\":[\t     {\"totalCredits\":\"2869.9999972222217\",\n\t      \"details\":[{\n\t      \"id\":\"0\",\n\t      \"unitPrice\":\"0.01\",\n\t      \"startTime\":\"1342735200000\",\n\t      \"endTime\":\"1343768399999\",\n\t      \"ellapsedTime\":\"1033199999\",\n\t      \"credits\":\"2869.9999972222217\"\n\t    \t}]\n\t    }\n\t]\n  }]\n}"
+
   def main(args: Array[String]) = {
+     val b : BillEntry = StdConverters.AllConverters.convertEx[BillEntry](CompactJsonTextFormat(jsonSample))
+     val l0 = b.bill
+     val l1 = aggregateResourceEntries(l0)
+
+     Console.err.println("Initial resources: ")
+     for{ i <- l0 } Console.err.println("RESOURCE: " + i.toJsonString)
+    Console.err.println("Aggregate resources: ")
+    for{ a <- l1 } {
+      Console.err.println("RESOURCE:  %s\n  %s\nEND RESOURCE".format(a.resourceName,a.toJsonString))
+    }
+
+    val aggr = new BillEntry(b.id,b.userID,b.status,b.remainingCredits,b.deductedCredits,b.startTime,b.endTime,l1)
+    Console.err.println("Aggregate:\n" + aggr.toJsonString)
+  }
+
+  //
+  def main0(args: Array[String]) = {
     //Console.err.println("JSON: " +  (new BillEntry).toJsonString)
     val propsfile = new FileStreamResource(new File("aquarium.properties"))
     var _props: Props = Props(propsfile)(StdConverters.AllConverters).getOr(Props()(StdConverters.AllConverters))