Parse credit plans and associate them with agreements
authorGeorgios Gousios <gousiosg@gmail.com>
Thu, 24 Nov 2011 12:36:58 +0000 (14:36 +0200)
committerGeorgios Gousios <gousiosg@gmail.com>
Thu, 24 Nov 2011 12:37:17 +0000 (14:37 +0200)
logic/src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSL.scala
logic/src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLAgreement.scala
logic/src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLCreditPlan.scala
logic/src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLPolicy.scala
logic/src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLTimeSpec.scala
logic/src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/Vocabulary.scala
logic/src/test/resources/policy.yaml
logic/src/test/scala/gr/grnet/aquarium/logic/test/DSLTest.scala

index 8a5c6e1..8871f76 100644 (file)
@@ -49,14 +49,21 @@ import java.util.Date
  */
 trait DSL extends Loggable {
 
+    /** An empty time frame*/
+    val emptyTimeFrame = DSLTimeFrame(new Date(0), None, Option(List()))
+
     /** An empty algorithm */
-   val emptyAlgorithm = DSLAlgorithm("", None, Map(), DSLTimeFrame(new Date(0), None, Option(List())))
+   val emptyAlgorithm = DSLAlgorithm("", None, Map(), emptyTimeFrame)
 
    /** An empty pricelist */
-   val emptyPriceList = DSLPriceList("", None, Map(), DSLTimeFrame(new Date(0), None, Option(List())))
+   val emptyPriceList = DSLPriceList("", None, Map(), emptyTimeFrame)
+
+  /** An empty creditplan */
+   val emptyCreditPlan = DSLCreditPlan("", None, 0,
+     List(DSLTimeSpec(0,0,-1,-1,-1)), emptyTimeFrame)
 
    /** An empty agreement*/
-   val emptyAgreement = DSLAgreement("", None, emptyAlgorithm, emptyPriceList)
+   val emptyAgreement = DSLAgreement("", None, emptyAlgorithm, emptyPriceList, emptyCreditPlan)
 
   /**
    * Parse an InputStream containing an Aquarium DSL algorithm.
@@ -78,14 +85,19 @@ trait DSL extends Loggable {
       resources, List()
     )
     logger.debug("Pricelists: %s".format(pricelists))
-    
+
+    val creditplans = parseCreditPlans(
+      policy./(Vocabulary.creditplans).asInstanceOf[YAMLListNode], List()
+    )
+    logger.debug("Creditplans: %s".format(creditplans))
+
     val agreements = parseAgreements(
       policy./(Vocabulary.agreements).asInstanceOf[YAMLListNode],
-      policies, pricelists, resources, List()
+      policies, pricelists, resources, creditplans, List()
     )
     logger.debug("Agreements: %s".format(agreements))
 
-    DSLPolicy(policies, pricelists, resources, agreements)
+    DSLPolicy(policies, pricelists, resources, creditplans, agreements)
   }
 
   /** Parse top level resources declarations */
@@ -231,17 +243,80 @@ trait DSL extends Loggable {
       case x: YAMLMapNode => parseTimeFrame(x)
       case YAMLEmptyNode => tmpl.equals(emptyAlgorithm) match {
         case false => tmpl.effective
-        case true => throw new DSLParseException(("Cannot find effectivity period for pricelist %s ").format(name))
+        case true => throw new DSLParseException(("Cannot find effectivity period for pricelist %s").format(name))
       }
     }
     DSLPriceList(name, overr, prices, timeframe)
   }
 
+  private def parseCreditPlans(creditsplans: YAMLListNode,
+                               results: List[DSLCreditPlan]) : List[DSLCreditPlan] = {
+    creditsplans.head match {
+      case YAMLEmptyNode => return List()
+      case _ =>
+    }
+
+    val superName = creditsplans.head / Vocabulary.overrides
+    val tmpl = superName match {
+      case y: YAMLStringNode =>
+        results.find(p => p.name.equals(y.string)) match {
+          case Some(x) => x
+          case None => throw new DSLParseException("Cannot find super credit plan %s".format(superName))
+        }
+      case YAMLEmptyNode => emptyCreditPlan
+      case _ => throw new DSLParseException("Super credit plan name %s not a string".format())
+    }
+
+    val plan = constructCreditPlan(creditsplans.head.asInstanceOf[YAMLMapNode], tmpl)
+
+    val tmpresults = results ++ List(plan)
+    List(plan) ++ parseCreditPlans(creditsplans.tail, tmpresults)
+  }
+
+  def constructCreditPlan(plan: YAMLMapNode, tmpl: DSLCreditPlan): DSLCreditPlan = {
+
+    val name = plan / Vocabulary.name match {
+      case x: YAMLStringNode => x.string
+      case YAMLEmptyNode => throw new DSLParseException(
+        "Credit plan does not have a name")
+    }
+
+    val overr = plan / Vocabulary.overrides match {
+      case x: YAMLStringNode => Some(tmpl)
+      case YAMLEmptyNode => None
+    }
+
+    val at = plan / Vocabulary.at match {
+      case x: YAMLStringNode => parseCronString(x.string)
+      case YAMLEmptyNode => throw new DSLParseException(
+        "Credit plan does not define repetition specifier")
+    }
+
+    val credits = plan / Vocabulary.credits match {
+      case x: YAMLIntNode => x.int.toFloat
+      case y: YAMLDoubleNode => y.double.toFloat
+      case YAMLEmptyNode => throw new DSLParseException(
+        "Credit plan does not have a name")
+    }
+
+    val timeframe = plan / Vocabulary.effective match {
+      case x: YAMLMapNode => parseTimeFrame(x)
+      case YAMLEmptyNode => tmpl.equals(emptyCreditPlan) match {
+        case false => tmpl.effective
+        case true => throw new DSLParseException(
+          ("Cannot find effectivity period for creditplan %s").format(name))
+      }
+    }
+
+    DSLCreditPlan(name, overr, credits, at, timeframe)
+  }
+
   /** Parse top level agreements */
   private def parseAgreements(agreements: YAMLListNode,
                       policies: List[DSLAlgorithm],
                       pricelists: List[DSLPriceList],
                       resources: List[DSLResource],
+                      creditplans: List[DSLCreditPlan],
                       results: List[DSLAgreement]): List[DSLAgreement] = {
      agreements.head match {
        case YAMLEmptyNode => return List()
@@ -260,18 +335,19 @@ trait DSL extends Loggable {
      }
 
      val agr = constructAgreement(agreements.head.asInstanceOf[YAMLMapNode],
-       tmpl, policies, pricelists, resources)
+       tmpl, policies, pricelists, resources, creditplans)
 
      val tmpresults = results ++ List(agr)
      List(agr) ++ parseAgreements(agreements.tail, policies, pricelists,
-       resources, tmpresults)
+       resources, creditplans, tmpresults)
    }
 
   def constructAgreement(agr: YAMLMapNode,
                          tmpl: DSLAgreement,
                          policies: List[DSLAlgorithm],
                          pricelists: List[DSLPriceList],
-                         resources: List[DSLResource]) : DSLAgreement = {
+                         resources: List[DSLResource],
+                         creditplans: List[DSLCreditPlan]) : DSLAgreement = {
      val name = agr / Vocabulary.name match {
       case x: YAMLStringNode => x.string
       case YAMLEmptyNode => throw new DSLParseException("Agreement does not have a name")
@@ -311,12 +387,29 @@ trait DSL extends Loggable {
       }
     }
 
+    val creditplan = agr / Vocabulary.creditplan match {
+      case x: YAMLStringNode => creditplans.find(p => p.name.equals(x.string)) match {
+        case Some(y) => y
+        case None => throw new DSLParseException(("Cannot find crediplan named %s").format(x))
+      }
+      case y: YAMLMapNode => tmpl.equals(emptyAgreement) match {
+        case true => throw new DSLParseException(("Incomplete creditplan definition for agreement %s").format(name))
+        case false =>
+          y.map += ("name" -> YAMLStringNode("/","%s-pricelist".format(name)))
+          constructCreditPlan(y, tmpl.creditplan)
+      }
+      case YAMLEmptyNode => tmpl.equals(emptyAgreement) match {
+        case true => throw new DSLParseException(("No creditplan for agreement %s").format(name))
+        case false => tmpl.creditplan
+      }
+    }
+
     val overrides = tmpl.equals(emptyAgreement) match {
       case true => Some(tmpl)
       case false => None
     }
 
-    DSLAgreement(name, overrides, algorithm, pricelist)
+    DSLAgreement(name, overrides, algorithm, pricelist, creditplan)
   }
 
   /** Parse a timeframe declaration */
index f160ebe..bdde45c 100644 (file)
@@ -46,5 +46,6 @@ case class DSLAgreement (
   name: String,
   overrides: Option[DSLAgreement],
   algorithm : DSLAlgorithm,
-  pricelist : DSLPriceList
+  pricelist : DSLPriceList,
+  creditplan: DSLCreditPlan
 )
\ No newline at end of file
index 809d956..b09ce88 100644 (file)
@@ -36,7 +36,8 @@
 package gr.grnet.aquarium.logic.accounting.dsl
 
 /**
- * A credit plan specifies 
+ * A credit plan specifies a periodic transfer of credits to the creditor's
+ * account.
  *
  * @author Georgios Gousios <gousiosg@gmail.com>
  */
@@ -45,6 +46,6 @@ case class DSLCreditPlan (
   name: String,
   overrides: Option[DSLCreditPlan],
   credits: Float,
-  at: DSLTimeSpec,
+  at: List[DSLTimeSpec],
   effective: DSLTimeFrame
 )
\ No newline at end of file
index a39c7a4..c0b9ae6 100644 (file)
@@ -44,6 +44,7 @@ case class DSLPolicy(
   algorithms: List[DSLAlgorithm],
   pricelists: List[DSLPriceList],
   resources: List[DSLResource],
+  creditplans: List[DSLCreditPlan],
   agreements: List[DSLAgreement]
 ) {
 
@@ -62,6 +63,11 @@ case class DSLPolicy(
     algorithms.find(a => a.name.equals(name))
   }
 
+  /**Find a crediplan by name */
+  def findCreditPlan(name: String): Option[DSLCreditPlan] = {
+    creditplans.find(a => a.name.equals(name))
+  }
+
   /**Find an agreement by name */
   def findAgreement(name: String): Option[DSLAgreement] = {
     agreements.find(a => a.name.equals(name))
index d48274b..eab65bf 100644 (file)
@@ -63,7 +63,7 @@ case class DSLTimeSpec(
   //Preconditions to force correct values on object creation
   assert(0 <= min && 60 > min)
   assert(0 <= hour && 24 > hour)
-  assert(-1 <= dom && 31 > dom && dom != 0)
+  assert(-1 <= dom && 31 > dom && dom != 0, "")
   assert(-1 <= mon && 12 > mon && mon != 0)
   assert(-1 <= dow && 7 > dow)
 
index c3b92aa..7b7b7bb 100644 (file)
@@ -40,6 +40,10 @@ package gr.grnet.aquarium.logic.accounting.dsl
  */
 object Vocabulary {
   val aquariumpolicy = "aquariumpolicy"
+  val creditplans = "creditplans"
+  val creditplan = "creditplan"
+  val at = "at"
+  val credits = "credits"
   val resources = "resources"
   val algorithms = "algorithms"
   val algorithm = "algorithm"
index 3d56b37..022488f 100644 (file)
@@ -67,12 +67,12 @@ aquariumpolicy:
     - creditplan:
       name: default
       credits: 100
-      at: "00 00 * * *"
+      at: "00 00 1 * *"
       effective:
         from: 0
     - creditplan:
       name: every10days
-      at: "00 00 0,10,20,30 * *"
+      at: "00 00 1,10,20,30 * *"
       credits: 20
       effective:
         from: 0
@@ -106,3 +106,4 @@ aquariumpolicy:
         effective:
           from: 1322061528 #Wed 23 Nov 2011 17:18
           to: 1324661839 #Fri, 23 Dec 2011 17:37
+
index ad7a70b..a4df60a 100644 (file)
@@ -79,6 +79,16 @@ class DSLTest extends DSL with TestMethods {
   }
 
   @Test
+  def testParseCreditPlans = {
+    before
+    assertEquals(2, creditpolicy.creditplans.size)
+    val plan = creditpolicy.findCreditPlan("every10days")
+    assertNotNone(plan)
+    assertEquals(20, plan.get.credits, 0.1F)
+    assertEquals(4, plan.get.at.size)
+  }
+
+  @Test
   def testCronParse = {
     var input = "12 12 * * *"
     var output = parseCronString(input)