From: Prodromos Gerakios Date: Fri, 3 Aug 2012 11:21:05 +0000 (+0300) Subject: Added REST functionality for the Bill. RabbitMQProducer was refined. UserActor sends... X-Git-Url: https://code.grnet.gr/git/aquarium/commitdiff_plain/d71800287f03e9b86b1d992d28107a67713ba1ff Added REST functionality for the Bill. RabbitMQProducer was refined. UserActor sends message only when state changes --- diff --git a/src/main/scala/gr/grnet/aquarium/actor/ActorRole.scala b/src/main/scala/gr/grnet/aquarium/actor/ActorRole.scala index 4f05fef..29d5fac 100644 --- a/src/main/scala/gr/grnet/aquarium/actor/ActorRole.scala +++ b/src/main/scala/gr/grnet/aquarium/actor/ActorRole.scala @@ -36,7 +36,7 @@ package gr.grnet.aquarium.actor import service.user.UserActor import gr.grnet.aquarium.actor.message.event.{ProcessIMEvent, ProcessResourceEvent} -import gr.grnet.aquarium.actor.message.{GetUserWalletRequest, GetUserStateRequest, GetUserBalanceRequest} +import message.{GetUserBillRequest, GetUserWalletRequest, GetUserStateRequest, GetUserBalanceRequest} import gr.grnet.aquarium.actor.message.config.{InitializeUserActorState, AquariumPropertiesLoaded, ActorConfigurationMessage} /** @@ -90,6 +90,7 @@ case object UserActorRole classOf[ProcessIMEvent], classOf[GetUserWalletRequest], classOf[GetUserBalanceRequest], + classOf[GetUserBillRequest], classOf[GetUserStateRequest]), Set(classOf[InitializeUserActorState], classOf[AquariumPropertiesLoaded])) diff --git a/src/main/scala/gr/grnet/aquarium/actor/message/GetUserBillRequest.scala b/src/main/scala/gr/grnet/aquarium/actor/message/GetUserBillRequest.scala new file mode 100644 index 0000000..7bf20b5 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/actor/message/GetUserBillRequest.scala @@ -0,0 +1,48 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.actor.message + +import gr.grnet.aquarium.logic.accounting.dsl.Timeslot + +/** + * + * @author Prodromos Gerakios + */ + +case class GetUserBillRequest(userID: String, timeslot : Timeslot, timestamp: Long) extends ActorMessage with UserActorRequestMessage { + def referenceTimeMillis = timestamp +} + diff --git a/src/main/scala/gr/grnet/aquarium/actor/message/GetUserBillResponse.scala b/src/main/scala/gr/grnet/aquarium/actor/message/GetUserBillResponse.scala new file mode 100644 index 0000000..d18cc7d --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/actor/message/GetUserBillResponse.scala @@ -0,0 +1,62 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.actor.message + +import gr.grnet.aquarium.AquariumInternalError +import gr.grnet.aquarium.charging.bill.BillEntry + +/** + * + * @author Prodromos Gerakios + */ + +case class GetUserBillResponse( + balance: Either[String, GetUserBillResponseData], + override val suggestedHTTPStatus: Int = 200) + extends UserActorResponseMessage({balance match { + case Left(s) => Left(s) + case Right(GetUserBillResponseData(userID,billEntry)) => Right(billEntry) + }}, suggestedHTTPStatus) { + def userID = balance match { + case Left(error) ⇒ + throw new AquariumInternalError("Could not obtain userID. %s".format(error)) + + case Right(data) ⇒ + data.userID + } +} + +case class GetUserBillResponseData(userID: String, billEntry: BillEntry) \ No newline at end of file diff --git a/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala b/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala index fc644fb..a8db62b 100644 --- a/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala +++ b/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala @@ -42,8 +42,12 @@ import gr.grnet.aquarium.actor._ import gr.grnet.aquarium.actor.message.event.{ProcessResourceEvent, ProcessIMEvent} import gr.grnet.aquarium.actor.message.config.{InitializeUserActorState, AquariumPropertiesLoaded} import gr.grnet.aquarium.util.date.TimeHelpers -import gr.grnet.aquarium.event.model.im.IMEventModel -import gr.grnet.aquarium.actor.message.{GetUserWalletResponseData, GetUserWalletResponse, GetUserWalletRequest, GetUserStateResponse, GetUserBalanceResponseData, GetUserBalanceResponse, GetUserStateRequest, GetUserBalanceRequest} +import gr.grnet.aquarium.event.model.im.{BalanceEvent, IMEventModel} +import message._ +import config.AquariumPropertiesLoaded +import config.InitializeUserActorState +import event.ProcessIMEvent +import event.ProcessResourceEvent import gr.grnet.aquarium.util.{LogHelpers, shortClassNameOf} import gr.grnet.aquarium.{Aquarium, AquariumInternalError} import gr.grnet.aquarium.computation.BillingMonthInfo @@ -52,6 +56,21 @@ import gr.grnet.aquarium.charging.state.{WorkingAgreementHistory, WorkingUserSta import gr.grnet.aquarium.charging.reason.{InitialUserActorSetup, RealtimeChargingReason} import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement} import gr.grnet.aquarium.event.model.resource.ResourceEventModel +import message.GetUserBalanceRequest +import message.GetUserBalanceResponse +import message.GetUserBalanceResponseData +import message.GetUserStateRequest +import message.GetUserStateResponse +import message.GetUserWalletRequest +import message.GetUserWalletResponse +import message.GetUserWalletResponseData +import scala.Left +import gr.grnet.aquarium.charging.state.WorkingAgreementHistory +import scala.Some +import scala.Right +import gr.grnet.aquarium.policy.StdUserAgreement +import gr.grnet.aquarium.charging.state.UserStateBootstrap +import gr.grnet.aquarium.charging.bill.BillEntry /** * @@ -359,6 +378,7 @@ class UserActor extends ReflectiveRoleableActor { updateLatestResourceEventIDFrom(rcEvent) } + var oldTotalCredits = this._workingUserState.totalCredits // FIXME check these if(nowYear != eventYear || nowMonth != eventMonth) { DEBUG( @@ -380,14 +400,27 @@ class UserActor extends ReflectiveRoleableActor { else { computeBatch() } - aquarium(Aquarium.EnvKeys.rabbitMQProducer). - sendMessage("{\"userid\": \"%s\", \"state\": %s}". - format(this._userID, - this._workingUserState.totalCredits >= 0.0)) + if(oldTotalCredits * this._workingUserState.totalCredits < 0) + BalanceEvent.send(aquarium,this._workingUserState.userID, + this._workingUserState.totalCredits>=0) DEBUG("Updated %s", this._workingUserState) logSeparator() } + def onGetUserBillRequest(event: GetUserBillRequest): Unit = { + try{ + val timeslot = event.timeslot + val state= if(haveWorkingUserState) Some(this._workingUserState) else None + val billEntry = BillEntry.fromWorkingUserState(timeslot,state) + val billData = GetUserBillResponseData(this._userID,billEntry) + sender ! GetUserBillResponse(Right(billData)) + } catch { + case e:Exception => + e.printStackTrace() + sender ! GetUserBillResponse(Left("Internal Server Error [AQU-BILL-0001]"), 500) + } + } + def onGetUserBalanceRequest(event: GetUserBalanceRequest): Unit = { val userID = event.userID diff --git a/src/main/scala/gr/grnet/aquarium/charging/bill/BillEntry.scala b/src/main/scala/gr/grnet/aquarium/charging/bill/BillEntry.scala new file mode 100644 index 0000000..3a33e71 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/charging/bill/BillEntry.scala @@ -0,0 +1,106 @@ +package gr.grnet.aquarium.charging.bill + +import gr.grnet.aquarium.charging.state.WorkingUserState +import gr.grnet.aquarium.util.json.JsonSupport +import com.ckkloverdos.resource.FileStreamResource +import java.io.File +import com.ckkloverdos.props.Props +import gr.grnet.aquarium.converter.{PrettyJsonTextFormat, StdConverters} +import gr.grnet.aquarium.{Aquarium, ResourceLocator, AquariumBuilder} +import gr.grnet.aquarium.store.memory.MemStoreProvider +import gr.grnet.aquarium.converter.StdConverters._ +import scala.Some +import gr.grnet.aquarium.logic.accounting.dsl.Timeslot + +/* +* Copyright 2011-2012 GRNET S.A. All rights reserved. +* +* Redistribution and use in source and binary forms, with or +* without modification, are permitted provided that the following +* conditions are met: +* +* 1. Redistributions of source code must retain the above +* copyright notice, this list of conditions and the following +* disclaimer. +* +* 2. Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials +* provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR +* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +* The views and conclusions contained in the software and +* documentation are those of the authors and should not be +* interpreted as representing official policies, either expressed +* or implied, of GRNET S.A. +*/ + + +/* +* @author Prodromos Gerakios +*/ + +class EventEntry(id:String, + eventType:String, + unitPrice:String, + startTime:String, + endTime:String, + ellapsedTime:String, + credits:String) extends JsonSupport { + +} + +class ResourceEntry(val resourceName : String, + val resourceType : String, + val unitName : String, + val totalCredits : String, + val details : List[EventEntry]) extends JsonSupport { + +} + +class BillEntry(val id:String, + val userID : String, + val status : String, + val remainingCredits:String, + val deductedCredits:String, + val startTime:String, + val endTime:String, + val bill:List[ResourceEntry] + ) extends JsonSupport { + +} + +object BillEntry { + def fromWorkingUserState(t:Timeslot,w:Option[WorkingUserState]) : BillEntry = { + //TODO: get entries at timeslot "t" + val eventEntry = new EventEntry("1234","onOff","0.1","323232323","3232223456","10000","5.00") + val resourceEntry = new ResourceEntry("VM_1","vmtime","0.01","5.0",List(eventEntry)) + new BillEntry("323232","loverdos@grnet.gr","ok","100.00","5.00","23023020302","23232323", + List(resourceEntry)) + } + + // + def main(args: Array[String]) = { + //Console.err.println("JSON: " + (new BillEntry).toJsonString) + val propsfile = new FileStreamResource(new File("a1.properties")) + var _props: Props = Props(propsfile)(StdConverters.AllConverters).getOr(Props()(StdConverters.AllConverters)) + val aquarium = new AquariumBuilder(_props, ResourceLocator.DefaultPolicyModel). + update(Aquarium.EnvKeys.storeProvider, new MemStoreProvider). + update(Aquarium.EnvKeys.eventsStoreFolder,Some(new File(".."))). + build() + aquarium.start() + () + } +} \ No newline at end of file diff --git a/src/main/scala/gr/grnet/aquarium/connector/rabbitmq/RabbitMQProducer.scala b/src/main/scala/gr/grnet/aquarium/connector/rabbitmq/RabbitMQProducer.scala index bb36eae..b77af94 100644 --- a/src/main/scala/gr/grnet/aquarium/connector/rabbitmq/RabbitMQProducer.scala +++ b/src/main/scala/gr/grnet/aquarium/connector/rabbitmq/RabbitMQProducer.scala @@ -1,9 +1,10 @@ package gr.grnet.aquarium.connector.rabbitmq import conf.RabbitMQConsumerConf +import conf.RabbitMQConsumerConf import conf.{RabbitMQKeys, RabbitMQConsumerConf} import conf.RabbitMQKeys.{RabbitMQConfKeys, RabbitMQConKeys} -import gr.grnet.aquarium.{Configurable, ResourceLocator, AquariumBuilder, Aquarium} +import gr.grnet.aquarium._ import com.rabbitmq.client._ import java.util.concurrent.atomic.{AtomicBoolean, AtomicReference} import com.ckkloverdos.props.Props @@ -12,6 +13,7 @@ import gr.grnet.aquarium.util.{Lock, Tags} import gr.grnet.aquarium.store.memory.MemStoreProvider import java.io.File import com.ckkloverdos.resource.FileStreamResource +import scala.Some /* @@ -49,6 +51,11 @@ import com.ckkloverdos.resource.FileStreamResource * or implied, of GRNET S.A. */ +/** + * + * @author Prodromos Gerakios + */ + class RabbitMQProducer extends Configurable { private[this] var _conf: RabbitMQConsumerConf = _ private[this] var _factory: ConnectionFactory = _ @@ -56,33 +63,29 @@ class RabbitMQProducer extends Configurable { private[this] var _channel: Channel = _ private[this] var _servers : Array[Address] = _ private[this] final val lock = new Lock() + private[this] var _exchangeName : String = _ + private[this] var _routingKey :String = _ def propertyPrefix: Option[String] = Some(RabbitMQKeys.PropertiesPrefix) // Some(RabbitMQConfKeys.imevents_credit) def configure(props: Props): Unit = { - var prop = props.get(RabbitMQConfKeys.imevents_credit).getOr("") - // Console.println("Prop: " + prop) - val Array(exchange, routing) = prop.split(":") - //Console.println("ex: " + exchange + " routing: " + routing) - _conf = RabbitMQConsumerConf( - tag = Tags.IMEventTag, - exchangeName = exchange, - routingKey = routing, - queueName = "", - connectionConf = RabbitMQKeys.makeConnectionConf(props), - exchangeConf = RabbitMQKeys.DefaultExchangeConf, - channelConf = RabbitMQKeys.DefaultChannelConf, - queueConf = RabbitMQKeys.DefaultQueueConf - ) + val propName = RabbitMQConfKeys.imevents_credit + def exn () = throw new AquariumInternalError(new Exception, "While obtaining value for key %s in properties".format(propName)) + val prop = props.get(propName).getOr(exn()) + if (prop.isEmpty) exn() + val connectionConf = RabbitMQKeys.makeConnectionConf(props) + val Array(exchangeName, routingKey) = prop.split(":") + _exchangeName = exchangeName + _routingKey = routingKey _factory = new ConnectionFactory - _factory.setConnectionTimeout(_conf.connectionConf(RabbitMQConKeys.reconnect_period_millis).toInt) - _factory.setUsername(_conf.connectionConf(RabbitMQConKeys.username)) - _factory.setPassword(_conf.connectionConf(RabbitMQConKeys.password)) - _factory.setVirtualHost(_conf.connectionConf(RabbitMQConKeys.vhost)) - _factory.setRequestedHeartbeat(_conf.connectionConf(RabbitMQConKeys.reconnect_period_millis).toInt) - _servers = _conf.connectionConf(RabbitMQConKeys.servers) + _factory.setConnectionTimeout(connectionConf(RabbitMQConKeys.reconnect_period_millis).toInt) + _factory.setUsername(connectionConf(RabbitMQConKeys.username)) + _factory.setPassword(connectionConf(RabbitMQConKeys.password)) + _factory.setVirtualHost(connectionConf(RabbitMQConKeys.vhost)) + _factory.setRequestedHeartbeat(connectionConf(RabbitMQConKeys.reconnect_period_millis).toInt) + _servers = connectionConf(RabbitMQConKeys.servers) } private[this] def withChannel[A]( next : => A) = { @@ -103,26 +106,8 @@ class RabbitMQProducer extends Configurable { def sendMessage(payload:String) = withChannel { - _channel.basicPublish(_conf.exchangeName, _conf.routingKey, + _channel.basicPublish(_exchangeName,_routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, payload.getBytes) } -} - -object RabbitMQProducer { - val propsfile = new FileStreamResource(new File("aquarium.properties")) - @volatile private[this] var _props: Props = Props(propsfile)(StdConverters.AllConverters).getOr(Props()(StdConverters.AllConverters)) - val aquarium = new AquariumBuilder(_props, ResourceLocator.DefaultPolicyModel). - update(Aquarium.EnvKeys.storeProvider, new MemStoreProvider). - update(Aquarium.EnvKeys.eventsStoreFolder,Some(new File(".."))). - build() - - - def main(args: Array[String]) = { - aquarium(Aquarium.EnvKeys.rabbitMQProducer). - sendMessage("{\"userid\": \"pgerakios@grnet.gr\", \"state\":true}") - Console.err.println("Message sent") - aquarium.stop() - () - } } \ No newline at end of file diff --git a/src/main/scala/gr/grnet/aquarium/service/FinagleRESTService.scala b/src/main/scala/gr/grnet/aquarium/service/FinagleRESTService.scala index 2f376a7..0273a6a 100644 --- a/src/main/scala/gr/grnet/aquarium/service/FinagleRESTService.scala +++ b/src/main/scala/gr/grnet/aquarium/service/FinagleRESTService.scala @@ -51,7 +51,7 @@ import java.net.InetSocketAddress import java.util.concurrent.{Executors, TimeUnit} import gr.grnet.aquarium.util.date.TimeHelpers import org.joda.time.format.ISODateTimeFormat -import gr.grnet.aquarium.actor.message.{GetUserWalletRequest, UserActorRequestMessage, GetUserStateRequest, GetUserBalanceRequest, UserActorResponseMessage} +import gr.grnet.aquarium.actor.message._ import com.ckkloverdos.resource.StreamResource import com.ckkloverdos.maybe.{Just, Failed} import gr.grnet.aquarium.event.model.ExternalEventModel @@ -59,6 +59,15 @@ import akka.util.{Timeout ⇒ ATimeout, Duration ⇒ ADuration} import akka.dispatch.{Future ⇒ AFuture} import com.fasterxml.jackson.databind.ObjectMapper import java.util +import scala.Left +import scala.Some +import com.ckkloverdos.maybe.Failed +import gr.grnet.aquarium.actor.message.GetUserStateRequest +import scala.Right +import com.ckkloverdos.maybe.Just +import gr.grnet.aquarium.actor.message.GetUserBalanceRequest +import gr.grnet.aquarium.actor.message.GetUserWalletRequest +import gr.grnet.aquarium.logic.accounting.dsl.Timeslot /** * @@ -310,6 +319,12 @@ class FinagleRESTService extends Lifecycle with AquariumAwareSkeleton with Confi case RESTPaths.UserWalletPath(userID) ⇒ // /user/(.+)/wallet/? callUserActor(GetUserWalletRequest(userID, millis)) + + case RESTPaths.UserBillPath(userID,st1,st2) ⇒ + val t1 = st1.toLong + val t2 = st2.toLong + val t = Timeslot(t1,if(t2==0)Long.MaxValue else t2) + callUserActor(GetUserBillRequest(userID,t,millis)) } val DefaultHandler: URIPF = { diff --git a/src/main/scala/gr/grnet/aquarium/service/RESTPaths.scala b/src/main/scala/gr/grnet/aquarium/service/RESTPaths.scala index e8c588b..6fe8202 100644 --- a/src/main/scala/gr/grnet/aquarium/service/RESTPaths.scala +++ b/src/main/scala/gr/grnet/aquarium/service/RESTPaths.scala @@ -57,24 +57,26 @@ object RESTPaths { final val ResourcesLogbackXMLPath = toResourcesPath(ResourceLocator.ResourceNames.LOGBACK_XML).r - final val ResourcesPolicyJSONPath = toResourcesPath(ResourceLocator.ResourceNames.POLICY_JSON).r + final val ResourcesPolicyJSONPath = toResourcesPath(ResourceLocator.ResourceNames.POLICY_JSON).r - final val ResourceEventPath = toEventPath("rcevent").r + final val ResourceEventPath = toEventPath("rcevent").r - final val IMEventPath = toEventPath("imevent").r + final val IMEventPath = toEventPath("imevent").r - /** - * Use this URI path to query for the user balance. The parenthesized regular expression part - * represents the user ID. - */ - final val UserBalancePath = "/user/([^/]+)/balance/?".r + /** + * Use this URI path to query for the user balance. The parenthesized regular expression part + * represents the user ID. + */ + final val UserBalancePath = "/user/([^/]+)/balance/?".r - /** - * Use this URI path to query for the user state. - */ - final val UserStatePath = "/user/([^/]+)/state/?".r + /** + * Use this URI path to query for the user state. + */ + final val UserStatePath = "/user/([^/]+)/state/?".r - final val UserWalletPath = "/user/([^/]+)/wallet/?".r + final val UserWalletPath = "/user/([^/]+)/wallet/?".r + + final val UserBillPath = "/user/([^/]+)/bill/([0-9]+)/([0-9]+)/?".r final val UserActorCacheContentsPath = (AdminPrefix + "/cache/actor/user/contents").r final val UserActorCacheCountPath = (AdminPrefix + "/cache/actor/user/size").r