Fix NPE with initial user state
[aquarium] / src / main / scala / gr / grnet / aquarium / charging / state / StdUserState.scala
1 /*
2  * Copyright 2011-2012 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.charging.state
37
38 import scala.collection.mutable
39 import gr.grnet.aquarium.computation.state.UserStateBootstrap
40 import gr.grnet.aquarium.policy.{ResourceType, UserAgreementModel}
41 import gr.grnet.aquarium.computation.BillingMonthInfo
42 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
43 import gr.grnet.aquarium.computation.state.parts.{ResourceInstanceAmount, AgreementHistory}
44 import gr.grnet.aquarium.charging.wallet.WalletEntry
45 import gr.grnet.aquarium.charging.reason.{InitialUserStateSetup, ChargingReason}
46 import gr.grnet.aquarium.AquariumInternalError
47
48 /**
49  *
50  * @author Christos KK Loverdos <loverdos@gmail.com>
51  */
52
53 final case class StdUserState(
54     id: String,
55     parentIDInStore: Option[String],
56     userID: String,
57     occurredMillis: Long,
58     totalCredits: Double,
59     theFullBillingMonth: Option[BillingMonthInfo],
60     chargingReason: ChargingReason,
61     previousResourceEvents: List[ResourceEventModel],
62     implicitlyIssuedStartEvents: List[ResourceEventModel],
63     accumulatingAmountOfResourceInstance: Map[String, Double],
64     chargingDataOfResourceInstance: Map[String, Map[String, Any]],
65     billingPeriodOutOfSyncResourceEventsCounter: Long,
66     agreementHistory: AgreementHistory,
67     walletEntries: List[WalletEntry]
68 ) extends UserStateModel {
69
70   def newWithChargingReason(newChargingReason: ChargingReason): UserStateModel = {
71     this.copy(chargingReason = newChargingReason)
72   }
73
74   private[this] def mutableMap[Vin, Vout](
75       inputMap: Map[String, Vin],
76       vInOut: Vin ⇒ Vout
77   ): mutable.Map[(String, String), Vout] = {
78     val items = for {
79       (resourceAndInstanceID, vIn) ← inputMap.toSeq
80     } yield {
81       StdUserState.resourceAndInstanceIDOfString(resourceAndInstanceID) -> vInOut(vIn)
82     }
83
84     mutable.Map(items: _*)
85   }
86
87   private[this] def mutableAccumulatingAmountMap: mutable.Map[(String, String), Double] = {
88     mutableMap(accumulatingAmountOfResourceInstance, identity[Double])
89   }
90
91   private[this] def mutableChargingDataMap: mutable.Map[(String, String), mutable.Map[String, Any]] = {
92     mutableMap(chargingDataOfResourceInstance, (vIn: Map[String, Any]) ⇒ mutable.Map(vIn.toSeq: _*))
93   }
94
95   private[this] def mutableImplicitlyIssuedStartMap: mutable.Map[(String, String), ResourceEventModel] = {
96     mutable.Map(implicitlyIssuedStartEvents.map(rem ⇒ (rem.safeResource, rem.safeInstanceID) -> rem): _*)
97   }
98
99   private[this] def mutablePreviousEventsMap: mutable.Map[(String, String), ResourceEventModel] = {
100     mutable.Map(previousResourceEvents.map(rem ⇒ (rem.safeResource, rem.safeInstanceID) -> rem): _*)
101   }
102
103   private[this] def mutableWalletEntries = {
104     val buffer = new mutable.ListBuffer[WalletEntry]
105     buffer ++= this.walletEntries
106     buffer
107   }
108
109   private[this] def mutableAgreementHistory = {
110     this.agreementHistory.toWorkingAgreementHistory
111   }
112
113   def toWorkingUserState(resourceTypesMap: Map[String, ResourceType]): WorkingUserState = {
114     new WorkingUserState(
115       this.userID,
116       this.parentIDInStore,
117       this.chargingReason,
118       resourceTypesMap,
119       mutablePreviousEventsMap,
120       mutableImplicitlyIssuedStartMap,
121       mutableAccumulatingAmountMap,
122       mutableChargingDataMap,
123       this.totalCredits,
124       mutableAgreementHistory,
125       this.occurredMillis,
126       this.billingPeriodOutOfSyncResourceEventsCounter,
127       mutableWalletEntries
128     )
129   }
130 }
131
132 final object StdUserState {
133   final val ResourceInstanceSeparator = "<:/:>"
134   final val ResourceInstanceSeparatorLength = ResourceInstanceSeparator.length
135
136   final def stringOfResourceAndInstanceID(resource: String, instanceID: String): String = {
137     def check(key: String, value: String) = {
138       if(value.indexOf(ResourceInstanceSeparator) != -1) {
139         throw new AquariumInternalError(
140           "The resource/instanceID separator '%s' is part of the %s '%s'".format(
141             ResourceInstanceSeparator, key, value
142           ))
143       }
144     }
145
146     check("resource type", resource)
147     check("resource instance ID", instanceID)
148
149     resource + ResourceInstanceSeparator + instanceID
150   }
151
152   final def resourceAndInstanceIDOfString(resourceAndInstanceID: String): (String, String) = {
153     val index = resourceAndInstanceID.indexOf(ResourceInstanceSeparator)
154     val resource = resourceAndInstanceID.substring(0, index)
155     val instanceID = resourceAndInstanceID.substring(index + ResourceInstanceSeparatorLength)
156
157     (resource, instanceID)
158   }
159
160   def createInitialUserState(
161       userID: String,
162       userCreationMillis: Long,
163       occurredMillis: Long,
164       totalCredits: Double,
165       initialAgreement: UserAgreementModel,
166       chargingReason: ChargingReason = InitialUserStateSetup(None)
167   ): StdUserState = {
168
169     StdUserState(
170       "",
171       None,
172       userID,
173       userCreationMillis,
174       totalCredits,
175       None,
176       chargingReason,
177       Nil,
178       Nil,
179       Map(),
180       Map(),
181       0L,
182       AgreementHistory.initial(initialAgreement),
183       Nil
184     )
185   }
186
187   def createInitialUserStateFromBootstrap(
188       usb: UserStateBootstrap,
189       occurredMillis: Long,
190       chargingReason: ChargingReason
191   ): StdUserState = {
192
193     createInitialUserState(
194       usb.userID,
195       usb.userCreationMillis,
196       occurredMillis,
197       usb.initialCredits,
198       usb.initialAgreement,
199       chargingReason
200     )
201   }
202 }
203