1 package gr.grnet.aquarium.charging.bill
3 import gr.grnet.aquarium.charging.state.WorkingUserState
4 import gr.grnet.aquarium.util.json.JsonSupport
5 import com.ckkloverdos.resource.FileStreamResource
7 import com.ckkloverdos.props.Props
8 import gr.grnet.aquarium.converter.{CompactJsonTextFormat, PrettyJsonTextFormat, StdConverters}
9 import gr.grnet.aquarium.{Aquarium, ResourceLocator, AquariumBuilder}
10 import gr.grnet.aquarium.store.memory.MemStoreProvider
11 import gr.grnet.aquarium.converter.StdConverters._
13 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
14 import java.util.concurrent.atomic.AtomicLong
15 import java.util.{Date, Calendar, GregorianCalendar}
16 import gr.grnet.aquarium.charging.wallet.WalletEntry
17 import scala.collection.parallel.mutable
18 import scala.collection.mutable.ListBuffer
19 import gr.grnet.aquarium.Aquarium.EnvKeys
20 import gr.grnet.aquarium.charging.Chargeslot
21 import scala.collection.immutable.TreeMap
23 import gr.grnet.aquarium.charging.Chargeslot
27 * Copyright 2011-2012 GRNET S.A. All rights reserved.
29 * Redistribution and use in source and binary forms, with or
30 * without modification, are permitted provided that the following
33 * 1. Redistributions of source code must retain the above
34 * copyright notice, this list of conditions and the following
37 * 2. Redistributions in binary form must reproduce the above
38 * copyright notice, this list of conditions and the following
39 * disclaimer in the documentation and/or other materials
40 * provided with the distribution.
42 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
43 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
44 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
45 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
46 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
47 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
48 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
49 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
50 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
51 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
52 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
53 * POSSIBILITY OF SUCH DAMAGE.
55 * The views and conclusions contained in the software and
56 * documentation are those of the authors and should not be
57 * interpreted as representing official policies, either expressed
58 * or implied, of GRNET S.A.
63 * @author Prodromos Gerakios <pgerakios@grnet.gr>
66 case class ChargeEntry(val id:String,
70 val ellapsedTime:String,
72 extends JsonSupport {}
75 class EventEntry(val eventType : String,
76 val details : List[ChargeEntry])
77 extends JsonSupport {}
80 case class ResourceEntry(val resourceName : String,
81 val resourceType : String,
82 val unitName : String,
83 val totalCredits : String,
84 val details : List[EventEntry])
85 extends JsonSupport {}
88 abstract class AbstractBillEntry
89 extends JsonSupport {}
91 class BillEntry(val id:String,
94 val remainingCredits:String,
95 val deductedCredits:String,
98 val bill:List[ResourceEntry]
100 extends AbstractBillEntry {}
103 object AbstractBillEntry {
105 private[this] val counter = new AtomicLong(0L)
106 private[this] def nextUIDObject() = counter.getAndIncrement
108 /*private[this] def walletTimeslot(i:WalletEntry) : Timeslot = {
109 val cal = new GregorianCalendar
110 cal.set(i.billingYear,i.billingMonth,1)
111 val dstart = cal.getTime
112 val lastDate = cal.getActualMaximum(Calendar.DATE)
113 cal.set(Calendar.DATE, lastDate)
114 val dend = cal.getTime
115 Timeslot(dstart,dend)
118 private[this] def toChargeEntry(c:Chargeslot) : ChargeEntry = {
119 val unitPrice = c.unitPrice.toString
120 val startTime = c.startMillis.toString
121 val endTime = c.stopMillis.toString
122 val difTime = (c.stopMillis - c.startMillis).toString
123 val credits = c.creditsToSubtract.toString
124 new ChargeEntry(counter.getAndIncrement.toString,unitPrice,
125 startTime,endTime,difTime,credits)
128 private[this] def toEventEntry(eventType:String,c:Chargeslot) : EventEntry =
129 new EventEntry(eventType,List(toChargeEntry(c)))
132 private[this] def toResourceEntry(w:WalletEntry) : ResourceEntry = {
133 assert(w.sumOfCreditsToSubtract==0.0 || w.chargslotCount > 0)
134 val rcType = w.resourceType.name
135 val rcName = rcType match {
137 w.currentResourceEvent.details("path")
139 w.currentResourceEvent.instanceID
141 val rcUnitName = w.resourceType.unit
142 val eventEntry = new ListBuffer[EventEntry]
143 val credits = w.sumOfCreditsToSubtract
144 val eventType = //TODO: This is hardcoded; find a better solution
147 val action = w.currentResourceEvent.details("action")
148 val path = w.currentResourceEvent.details("path")
149 "%s@%s".format(action,path)
151 w.currentResourceEvent.value.toInt match {
165 for { c <- w.chargeslots }{
166 if(c.creditsToSubtract != 0.0) {
167 //Console.err.println("c.creditsToSubtract : " + c.creditsToSubtract)
168 eventEntry += toEventEntry(eventType.toString,c)
169 //credits += c.creditsToSubtract
172 //Console.err.println("TOTAL resource event credits: " + credits)
173 new ResourceEntry(rcName,rcType,rcUnitName,credits.toString,eventEntry.toList)
176 private[this] def resourceEntriesAt(t:Timeslot,w:WorkingUserState) : (List[ResourceEntry],Double) = {
177 val ret = new ListBuffer[ResourceEntry]
179 //Console.err.println("Wallet entries: " + w.walletEntries)
180 val walletEntries = w.walletEntries
181 for { i <- walletEntries} {
182 if(t.contains(i.referenceTimeslot) && i.sumOfCreditsToSubtract != 0.0){
183 //Console.err.println("i.sumOfCreditsToSubtract : " + i.sumOfCreditsToSubtract)
184 if(i.sumOfCreditsToSubtract > 0.0D) sum += i.sumOfCreditsToSubtract
185 ret += toResourceEntry(i)
187 //Console.err.println("WALLET ENTERY : " + i + "\n" +
188 // t + " does not contain " + i.referenceTimeslot + " !!!!")
194 private[this] def aggregateResourceEntries(re:List[ResourceEntry]) : List[ResourceEntry] = {
195 def addResourceEntries(a:ResourceEntry,b:ResourceEntry) : ResourceEntry = {
196 assert(a.resourceName == b.resourceName)
197 val totalCredits = (a.totalCredits.toDouble+b.totalCredits.toDouble).toString
198 a.copy(a.resourceName,a.resourceType,a.unitName,totalCredits,a.details ::: b.details)
200 re.foldLeft(TreeMap[String,ResourceEntry]()){ (map,r1) =>
201 map.get(r1.resourceName) match {
202 case None => map + ((r1.resourceName,r1))
203 case Some(r0) => (map - r0.resourceName) +
204 ((r0.resourceName, addResourceEntries(r0,r1)))
209 def fromWorkingUserState(t:Timeslot,userID:String,w:Option[WorkingUserState]) : AbstractBillEntry = {
212 new BillEntry(counter.getAndIncrement.toString,
216 t.from.getTime.toString,t.to.getTime.toString,
219 val (rcEntries,rcEntriesCredits) = resourceEntriesAt(t,w)
220 val resMap = aggregateResourceEntries(rcEntries)
221 new BillEntry(counter.getAndIncrement.toString,
223 w.totalCredits.toString,
224 rcEntriesCredits.toString,
225 t.from.getTime.toString,t.to.getTime.toString,
228 //Console.err.println("JSON: " + ret.toJsonString)
232 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}"
234 def main0(args: Array[String]) = {
235 val b : BillEntry = StdConverters.AllConverters.convertEx[BillEntry](CompactJsonTextFormat(jsonSample))
237 val l1 = aggregateResourceEntries(l0)
239 Console.err.println("Initial resources: ")
240 for{ i <- l0 } Console.err.println("RESOURCE: " + i.toJsonString)
241 Console.err.println("Aggregate resources: ")
243 Console.err.println("RESOURCE: %s\n %s\nEND RESOURCE".format(a.resourceName,a.toJsonString))
246 val aggr = new BillEntry(b.id,b.userID,b.status,b.remainingCredits,b.deductedCredits,b.startTime,b.endTime,l1)
247 Console.err.println("Aggregate:\n" + aggr.toJsonString)
251 def main(args: Array[String]) = {
252 //Console.err.println("JSON: " + (new BillEntry).toJsonString)
253 val propsfile = new FileStreamResource(new File("aquarium.properties"))
254 var _props: Props = Props(propsfile)(StdConverters.AllConverters).getOr(Props()(StdConverters.AllConverters))
255 val aquarium = new AquariumBuilder(_props, ResourceLocator.DefaultPolicyModel).
256 update(Aquarium.EnvKeys.storeProvider, new MemStoreProvider).
257 update(Aquarium.EnvKeys.eventsStoreFolder,Some(new File(".."))).