2 * Copyright 2011-2012 GRNET S.A. All rights reserved.
4 * Redistribution and use in source and binary forms, with or
5 * without modification, are permitted provided that the following
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
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.
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.
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.
36 package gr.grnet.aquarium.charging
38 import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
39 import gr.grnet.aquarium.computation.{TimeslotComputations, BillingMonthInfo}
40 import gr.grnet.aquarium.event.{CreditsModel, DetailsModel}
41 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
42 import gr.grnet.aquarium.message.avro.gen.{EffectivePriceTableMsg, FullPriceTableMsg, ResourceTypeMsg, WalletEntryMsg, ResourceInstanceChargingStateMsg, ResourcesChargingStateMsg, ResourceEventMsg}
43 import gr.grnet.aquarium.message.avro.{MessageHelpers, AvroHelpers, MessageFactory}
44 import gr.grnet.aquarium.policy.{PolicyModel, EffectivePriceTableModel, FullPriceTableModel, UserAgreementModel}
45 import gr.grnet.aquarium.store.PolicyStore
46 import gr.grnet.aquarium.util._
47 import gr.grnet.aquarium.util.date.TimeHelpers
48 import java.{util ⇒ ju}
49 import java.util.{List ⇒ JList, ArrayList ⇒ JArrayList}
50 import scala.collection.immutable
51 import scala.collection.mutable
52 import gr.grnet.aquarium.message.MessageConstants
56 * A charging behavior indicates how charging for a resource will be done
57 * wrt the various states a resource can be.
59 * @author Christos KK Loverdos <loverdos@gmail.com>
62 abstract class ChargingBehaviorSkeleton(
63 final val selectorLabelsHierarchy: List[String]
64 ) extends ChargingBehavior with Loggable {
66 final val HourMillis = CreditsModel.from(1000L * 60 * 60)
67 final val HourMillisInverse = CreditsModel.inv(HourMillis)
68 final val MB = CreditsModel.from(1024L * 1024L)
69 final val MBInverse = CreditsModel.inv(MB)
70 final val GB = CreditsModel.from(1024L * 1024L * 1024L)
71 final val GBInverse = CreditsModel.inv(GB)
73 @inline final def HrsOfMillis(timeDeltaMillis: Long): CreditsModel.Type = {
76 CreditsModel.from(timeDeltaMillis)
80 @inline final def MBsOfBytes(bytes: Double): CreditsModel.Type = {
83 CreditsModel.from(bytes)
87 @inline final protected def rcDebugInfo(rcEvent: ResourceEventMsg) = {
88 AvroHelpers.jsonStringOfSpecificRecord(rcEvent)
91 protected def newResourceInstanceChargingStateMsg(
97 MessageFactory.newResourceInstanceChargingStateMsg(
100 new JArrayList[ResourceEventMsg](),
101 new JArrayList[ResourceEventMsg](),
112 final protected def ensureInitializedWorkingState(
113 resourcesChargingState: ResourcesChargingStateMsg,
114 resourceEvent: ResourceEventMsg
116 ensureInitializedResourcesChargingStateDetails(resourcesChargingState.getDetails)
117 ensureInitializedResourceInstanceChargingState(resourcesChargingState, resourceEvent)
120 protected def ensureInitializedResourcesChargingStateDetails(details: DetailsModel.Type) {}
122 protected def ensureInitializedResourceInstanceChargingState(
123 resourcesChargingState: ResourcesChargingStateMsg,
124 resourceEvent: ResourceEventMsg
127 val instanceID = resourceEvent.getInstanceID
128 val clientID = resourceEvent.getClientID
129 val resource = resourceEvent.getResource
130 val stateOfResourceInstance = resourcesChargingState.getStateOfResourceInstance
132 stateOfResourceInstance.get(instanceID) match {
134 stateOfResourceInstance.put(
136 newResourceInstanceChargingStateMsg(clientID, resource, instanceID)
143 protected def fillWorkingResourceInstanceChargingStateFromEvent(
144 resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
145 resourceEvent: ResourceEventMsg
148 resourceInstanceChargingState.setCurrentValue(resourceEvent.getValue.toString.toDouble)
151 protected def computeWalletEntriesForNewEvent(
152 resourceEvent: ResourceEventMsg,
153 resourceType: ResourceTypeMsg,
154 billingMonthInfo: BillingMonthInfo,
155 totalCredits: Double,
156 referenceStartMillis: Long,
157 referenceStopMillis: Long,
158 agreementByTimeslot: immutable.SortedMap[Timeslot, UserAgreementModel],
159 workingResourcesChargingStateDetails: DetailsModel.Type,
160 resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
162 walletEntryRecorder: WalletEntryMsg ⇒ Unit
165 val userID = resourceEvent.getUserID
166 val resourceEventDetails = resourceEvent.getDetails
168 var _oldTotalCredits = totalCredits
170 var _newAccumulatingAmount = computeNewAccumulatingAmount(resourceInstanceChargingState, resourceEventDetails)
171 // It will also update the old one inside the data structure.
172 resourceInstanceChargingState.setOldAccumulatingAmount(resourceInstanceChargingState.getAccumulatingAmount)
173 resourceInstanceChargingState.setAccumulatingAmount(_newAccumulatingAmount)
175 val policyByTimeslot = aquarium.policyStore.loadSortedPolicyModelsWithin(
176 referenceStartMillis,
180 val effectivePriceTableModelSelector: FullPriceTableModel ⇒ EffectivePriceTableModel = fullPriceTable ⇒ {
181 this.selectEffectivePriceTableModel(
183 workingResourcesChargingStateDetails,
184 resourceInstanceChargingState,
186 referenceStartMillis,
192 val fullPriceTableModelGetter = aquarium.unsafeFullPriceTableModelForAgreement(_,_)
194 val initialChargeslots = TimeslotComputations.computeInitialChargeslots(
195 Timeslot(referenceStartMillis, referenceStopMillis),
198 fullPriceTableModelGetter,
199 effectivePriceTableModelSelector
202 val fullChargeslots = initialChargeslots.map { cs ⇒
203 val timeDeltaMillis = cs.getStopMillis - cs.getStartMillis
205 val (creditsToSubtract, explanation) = this.computeCreditsToSubtract(
206 resourceInstanceChargingState,
207 _oldTotalCredits, // FIXME ??? Should recalculate ???
212 cs.setCreditsToSubtract(creditsToSubtract)
213 cs.setExplanation(explanation)
218 if(fullChargeslots.length == 0) {
219 throw new AquariumInternalError("No chargeslots computed for resource event %s".format(resourceEvent.getOriginalID))
222 val sumOfCreditsToSubtract = fullChargeslots.map(_.getCreditsToSubtract.toDouble).sum
223 val newTotalCredits = _oldTotalCredits - sumOfCreditsToSubtract
225 val eventsForWallet = new ju.ArrayList[ResourceEventMsg](resourceInstanceChargingState.getPreviousEvents)
226 eventsForWallet.add(0, resourceEvent)
227 import scala.collection.JavaConverters.seqAsJavaListConverter
228 val newWalletEntry = MessageFactory.newWalletEntryMsg(
230 CreditsModel.from(sumOfCreditsToSubtract),
231 CreditsModel.from(_oldTotalCredits),
232 CreditsModel.from(newTotalCredits),
233 TimeHelpers.nowMillis(),
234 referenceStartMillis,
236 billingMonthInfo.year,
237 billingMonthInfo.month,
238 billingMonthInfo.day,
239 fullChargeslots.asJava,
242 resourceEvent.getIsSynthetic
245 logger.debug("newWalletEntry = {}", AvroHelpers.jsonStringOfSpecificRecord(newWalletEntry))
247 walletEntryRecorder.apply(newWalletEntry)
249 (1, sumOfCreditsToSubtract)
253 def selectEffectivePriceTableModel(
254 fullPriceTable: FullPriceTableModel,
255 chargingBehaviorDetails: DetailsModel.Type,
256 resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
257 currentResourceEvent: ResourceEventMsg,
258 referenceStartMillis: Long,
259 referenceStopMillis: Long,
261 ): EffectivePriceTableModel = {
263 val selectorPath = computeSelectorPath(
264 chargingBehaviorDetails,
265 resourceInstanceChargingState,
266 currentResourceEvent,
267 referenceStartMillis,
272 fullPriceTable.effectivePriceTableOfSelectorForResource(
274 currentResourceEvent.getResource,
279 final protected def constructDummyFirstEventFor(
280 actualFirst: ResourceEventMsg,
281 newOccurredMillis: Long,
283 ): ResourceEventMsg = {
285 val dm = DetailsModel.make
286 DetailsModel.setBoolean(dm, MessageConstants.DetailsKeys.aquarium_is_synthetic)
287 DetailsModel.setBoolean(dm, MessageConstants.DetailsKeys.aquarium_is_dummy_first)
288 DetailsModel.setString(dm, MessageConstants.DetailsKeys.aquarium_reference_event_id, actualFirst.getOriginalID)
289 DetailsModel.setString(dm, MessageConstants.DetailsKeys.aquarium_reference_event_id_in_store, actualFirst.getInStoreID)
291 ResourceEventMsg.newBuilder(actualFirst).
294 setOccurredMillis(newOccurredMillis).
295 setReceivedMillis(newOccurredMillis).