Refactor to accomodate MasterConf rename
[aquarium] / src / main / scala / gr / grnet / aquarium / logic / accounting / Accounting.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.accounting
37
38 import dsl._
39 import gr.grnet.aquarium.logic.events.{WalletEntry, ResourceEvent}
40 import collection.immutable.SortedMap
41 import gr.grnet.aquarium.Configurator._
42 import gr.grnet.aquarium.util.Loggable
43 import com.ckkloverdos.maybe.{Maybe, Failed, NoVal, Just}
44
45 /**
46  * 
47  *
48  * @author Georgios Gousios <gousiosg@gmail.com>
49  */
50 trait Accounting extends DSLUtils with Loggable {
51   
52   def chargeEvent(ev: ResourceEvent) : Maybe[List[WalletEntry]] = {
53
54     if (!ev.validate())
55       Failed(new AccountingException("Event not valid"))
56
57     val userState = MasterConfigurator.userStateStore.findUserStateByUserId(ev.userId) match {
58       case Just(x) => x
59       case NoVal =>
60         return Failed(new AccountingException("Inexistent user: %s".format(ev.toJson)))
61       case Failed(x,y) =>
62         return Failed(new AccountingException("Error retrieving user state: %s".format(x)))
63     }
64
65     val agreement = userState.agreement.data.name
66
67     val agr = Policy.policy.findAgreement(agreement) match {
68       case Some(x) => x
69       case None => return Failed(
70         new AccountingException("Cannot find agreement:%s".format(agreement)))
71     }
72
73     val resource = Policy.policy.findResource(ev.resource).get
74
75     //val chargeChunks = calcChangeChunks(agr, ev.value, ev.resource, )
76
77
78     Just(List(WalletEntry.zero))
79   }
80
81   private[logic] case class ChargeChunk(value: Float, algorithm: String,
82                                         price: Float, when: Timeslot) {
83     assert(value > 0)
84     assert(!algorithm.isEmpty)
85
86     def cost(): Float = {
87       //TODO: Apply the algorithm when we start parsing it
88       value * price
89     }
90   }
91
92   private def calcChangeChunks(agr: DSLAgreement, volume: Float,
93                                res: DSLResource, t: Timeslot): List[ChargeChunk] = {
94
95     val alg = resolveEffectiveAlgorithmsForTimeslot(t, agr)
96     val pri = resolveEffectivePricelistsForTimeslot(t, agr)
97     val chunks = splitChargeChunks(alg, pri)
98
99     val algChunked = chunks._1
100     val priChunked = chunks._2
101
102     assert(algChunked.size == priChunked.size)
103     val totalTime = t.from.getTime - t.to.getTime
104     algChunked.keySet.map{
105       x =>
106         val amount = volume * (totalTime / (x.to.getTime - x.from.getTime))
107         new ChargeChunk(amount,
108           algChunked.get(x).get.algorithms.getOrElse(res, ""),
109           priChunked.get(x).get.prices.getOrElse(res, 0F), x)
110     }.toList
111   }
112
113   /**
114    * Align charge timeslots between algorithms and pricelists. As algorithm
115    * and pricelists can have different effectivity periods, this method
116    * examines them and splits them as necessary.
117    */
118   private[logic] def splitChargeChunks(alg: SortedMap[Timeslot, DSLAlgorithm],
119                         price: SortedMap[Timeslot, DSLPriceList]) :
120     (Map[Timeslot, DSLAlgorithm], Map[Timeslot, DSLPriceList]) = {
121
122     val zipped = alg.keySet.zip(price.keySet)
123
124     zipped.find(p => !p._1.equals(p._2)) match {
125       case None => (alg, price)
126       case Some(x) =>
127         val algTimeslot = x._1
128         val priTimeslot = x._2
129
130         assert(algTimeslot.from == priTimeslot.from)
131
132         if (algTimeslot.endsAfter(priTimeslot)) {
133           val slices = algTimeslot.slice(priTimeslot.to)
134           val algo = alg.get(algTimeslot).get
135           val newalg = alg - algTimeslot ++ Map(slices.apply(0) -> algo) ++ Map(slices.apply(1) -> algo)
136           splitChargeChunks(newalg, price)
137         }
138         else {
139           val slices = priTimeslot.slice(priTimeslot.to)
140           val pl = price.get(priTimeslot).get
141           val newPrice = price - priTimeslot ++ Map(slices.apply(0) -> pl) ++ Map(slices.apply(1) -> pl)
142           splitChargeChunks(alg, newPrice)
143         }
144     }
145   }
146 }
147
148 /** An exception raised when something goes wrong with accounting */
149 class AccountingException(msg: String) extends Exception(msg)