Add two more REST internal calls
authorChristos KK Loverdos <loverdos@gmail.com>
Fri, 8 Jun 2012 14:49:24 +0000 (17:49 +0300)
committerChristos KK Loverdos <loverdos@gmail.com>
Fri, 8 Jun 2012 14:49:24 +0000 (17:49 +0300)
- Get rc event by id.
- Get im event by id.

Calls are protected by the admin.cookie property

src/main/scala/gr/grnet/aquarium/actor/service/rest/RESTActor.scala
src/main/scala/gr/grnet/aquarium/actor/service/rest/RESTPaths.scala
src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala
src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala
src/main/scala/gr/grnet/aquarium/computation/state/parts/IMStateSnapshot.scala
src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLResourcesMap.scala
src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStore.scala

index 9f1b317..ec6dffd 100644 (file)
@@ -52,6 +52,7 @@ import gr.grnet.aquarium.{ResourceLocator, Aquarium}
 import com.ckkloverdos.resource.StreamResource
 import com.ckkloverdos.maybe.{Failed, NoVal, Just}
 import java.net.InetAddress
+import gr.grnet.aquarium.event.model.{ExternalEventModel, EventModel}
 
 /**
  * Spray-based REST service. This is the outer-world's interface to Aquarium functionality.
@@ -63,9 +64,13 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable {
 
   self.id = _id
 
+  final val TEXT_PLAIN       = "text/plain"
+  final val APPLICATION_JSON = "application/json"
+
+
   private[this] def aquarium = Aquarium.Instance
 
-  private def stringResponse(status: Int, stringBody: String, contentType: String = "application/json"): HttpResponse = {
+  private def stringResponse(status: Int, stringBody: String, contentType: String): HttpResponse = {
     HttpResponse(
       status,
       HttpHeader("Content-type", "%s;charset=utf-8".format(contentType)) :: Nil,
@@ -86,13 +91,31 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable {
     res match {
       case Failed(e) ⇒
         logger.error("While serving %s".format(uri), e)
-        responder.complete(stringResponse(501, "Internal Server Error: %s".format(shortInfoOf(e)), "text/plain"))
+        responder.complete(stringResponse(501, "Internal Server Error: %s".format(shortInfoOf(e)), TEXT_PLAIN))
 
       case _ ⇒
 
     }
   }
 
+  private def eventInfoResponse[E <: ExternalEventModel](
+      uri: String,
+      responder: RequestResponder,
+      getter: String ⇒ Option[E],
+      eventID: String
+  ): Unit = {
+
+    val toSend = getter.apply(eventID) match {
+     case Some(event) ⇒
+       (200, event.toJsonString, APPLICATION_JSON)
+
+     case None ⇒
+       (404, "Event not found", TEXT_PLAIN)
+   }
+
+   responder.complete(stringResponse(toSend._1, toSend._2, toSend._3))
+  }
+
   def withAdminCookie(
       uri: String,
       responder: RequestResponder,
@@ -108,24 +131,24 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable {
             catch {
               case e: Throwable ⇒
                 logger.error("While serving %s".format(uri), e)
-                responder.complete(stringResponse(501, "Internal Server Error: %s".format(shortInfoOf(e)), "text/plain"))
+                responder.complete(stringResponse(501, "Internal Server Error: %s".format(shortInfoOf(e)), TEXT_PLAIN))
             }
 
           case Some(cookieHeader) ⇒
             logger.warn("Admin request %s with bad cookie '%s' from %s".format(uri, cookieHeader.value, remoteAddress))
-            responder.complete(stringResponse(401, "Unauthorized!", "text/plain"))
+            responder.complete(stringResponse(401, "Unauthorized!", TEXT_PLAIN))
 
           case None ⇒
             logger.warn("Admin request %s with no cookie from %s".format(uri, remoteAddress))
-            responder.complete(stringResponse(401, "Unauthorized!", "text/plain"))
+            responder.complete(stringResponse(401, "Unauthorized!", TEXT_PLAIN))
         }
 
       case NoVal ⇒
-        responder.complete(stringResponse(403, "Forbidden!", "text/plain"))
+        responder.complete(stringResponse(403, "Forbidden!", TEXT_PLAIN))
     }
   }
 
-  private def stringResponse200(stringBody: String, contentType: String = "application/json"): HttpResponse = {
+  private def stringResponse200(stringBody: String, contentType: String): HttpResponse = {
     stringResponse(200, stringBody, contentType)
   }
 
@@ -133,7 +156,7 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable {
     case RequestContext(HttpRequest(GET, "/ping", _, _, _), _, responder) ⇒
       val now = TimeHelpers.nowMillis()
       val nowFormatted = ISODateTimeFormat.dateTime().print(now)
-      responder.complete(stringResponse200("PONG\n%s\n%s".format(now, nowFormatted), "text/plain"))
+      responder.complete(stringResponse200("PONG\n%s\n%s".format(now, nowFormatted), TEXT_PLAIN))
 
     case RequestContext(HttpRequest(GET, "/stats", _, _, _), _, responder) ⇒ {
       (serverActor ? GetStats).mapTo[Stats].onComplete {
@@ -142,18 +165,23 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable {
             case Right(stats) => responder.complete {
               stringResponse200(
                 "Uptime              : " + (stats.uptime / 1000.0) + " sec\n" +
-                  "Requests dispatched : " + stats.requestsDispatched + '\n' +
-                  "Requests timed out  : " + stats.requestsTimedOut + '\n' +
-                  "Requests open       : " + stats.requestsOpen + '\n' +
-                  "Open connections    : " + stats.connectionsOpen + '\n'
+                "Requests dispatched : " + stats.requestsDispatched + '\n' +
+                "Requests timed out  : " + stats.requestsTimedOut + '\n' +
+                "Requests open       : " + stats.requestsOpen + '\n' +
+                "Open connections    : " + stats.connectionsOpen + '\n',
+                TEXT_PLAIN
               )
             }
-            case Left(ex) => responder.complete(stringResponse(500, "Couldn't get server stats due to " + ex, "text/plain"))
+            case Left(ex) => responder.complete(stringResponse(500, "Couldn't get server stats due to " + ex, TEXT_PLAIN))
           }
       }
     }
 
     case RequestContext(HttpRequest(GET, uri, headers, body, protocol), remoteAddress, responder) ⇒
+      def withAdminCookieHelper(f: RequestResponder ⇒ Unit): Unit = {
+        withAdminCookie(uri, responder, headers, remoteAddress)(f)
+      }
+
       //+ Main business logic REST URIs are matched here
       val millis = TimeHelpers.nowMillis()
       uri match {
@@ -166,32 +194,42 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable {
           callRouter(GetUserStateRequest(userId, millis), responder)
 
         case AdminPingAll() ⇒
-          withAdminCookie(uri, responder, headers, remoteAddress) { responder ⇒
+          withAdminCookieHelper { responder ⇒
             callRouter(PingAllRequest(), responder)
           }
 
         case ResourcesAquariumProperties() ⇒
-          withAdminCookie(uri, responder, headers, remoteAddress) { responder ⇒
-            resourceInfoResponse(uri, responder, ResourceLocator.Resources.AquariumPropertiesResource, "text/plain")
+          withAdminCookieHelper { responder ⇒
+            resourceInfoResponse(uri, responder, ResourceLocator.Resources.AquariumPropertiesResource, TEXT_PLAIN)
           }
 
         case ResourcesLogbackXML() ⇒
-          withAdminCookie(uri, responder, headers, remoteAddress) { responder ⇒
-            resourceInfoResponse(uri, responder, ResourceLocator.Resources.LogbackXMLResource, "text/plain")
+          withAdminCookieHelper { responder ⇒
+            resourceInfoResponse(uri, responder, ResourceLocator.Resources.LogbackXMLResource, TEXT_PLAIN)
           }
 
         case ResourcesPolicyYAML() ⇒
-          withAdminCookie(uri, responder, headers, remoteAddress) { responder ⇒
-            resourceInfoResponse(uri, responder, ResourceLocator.Resources.PolicyYAMLResource, "text/plain")
+          withAdminCookieHelper { responder ⇒
+            resourceInfoResponse(uri, responder, ResourceLocator.Resources.PolicyYAMLResource, TEXT_PLAIN)
+          }
+
+        case ResourceEventPath(id) ⇒
+          withAdminCookieHelper { responder ⇒
+            eventInfoResponse(uri, responder, aquarium.resourceEventStore.findResourceEventById, id)
+          }
+
+        case IMEventPath(id) ⇒
+          withAdminCookieHelper { responder ⇒
+            eventInfoResponse(uri, responder, aquarium.imEventStore.findIMEventById, id)
           }
 
         case _ ⇒
-          responder.complete(stringResponse(404, "Unknown resource!", "text/plain"))
+          responder.complete(stringResponse(404, "Unknown resource!", TEXT_PLAIN))
       }
     //- Main business logic REST URIs are matched here
 
     case RequestContext(HttpRequest(_, _, _, _, _), _, responder) ⇒
-      responder.complete(stringResponse(404, "Unknown resource!", "text/plain"))
+      responder.complete(stringResponse(404, "Unknown resource!", TEXT_PLAIN))
 
     case Timeout(method, uri, _, _, _, complete) ⇒ complete {
       HttpResponse(status = 500).withBody("The " + method + " request to '" + uri + "' has timed out...")
@@ -212,12 +250,12 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable {
             // TODO: Will this ever happen??
             logger.warn("Future did not complete for %s".format(message))
             val statusCode = 500
-            responder.complete(stringResponse(statusCode, "Internal Server Error", "text/plain"))
+            responder.complete(stringResponse(statusCode, "Internal Server Error", TEXT_PLAIN))
 
           case Some(Left(error)) ⇒
             val statusCode = 500
             logger.error("Error %s serving %s: %s".format(statusCode, message, error))
-            responder.complete(stringResponse(statusCode, "Internal Server Error", "text/plain"))
+            responder.complete(stringResponse(statusCode, "Internal Server Error", TEXT_PLAIN))
 
           case Some(Right(actualResponse)) ⇒
             actualResponse match {
@@ -232,20 +270,21 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable {
                       message,
                       actualResponse))
 
-                    responder.complete(stringResponse(statusCode, errorMessage, "text/plain"))
+                    responder.complete(stringResponse(statusCode, errorMessage, TEXT_PLAIN))
 
                   case Right(response) ⇒
                     responder.complete(
                       HttpResponse(
                         routerResponse.suggestedHTTPStatus,
                         body = routerResponse.responseToJsonString.getBytes("UTF-8"),
-                        headers = HttpHeader("Content-type", "application/json;charset=utf-8") :: Nil))
+                        headers = HttpHeader("Content-type", APPLICATION_JSON+";charset=utf-8") ::
+                          Nil))
                 }
 
               case _ ⇒
                 val statusCode = 500
                 logger.error("Error %s serving %s: Response is: %s".format(statusCode, message, actualResponse))
-                responder.complete(stringResponse(statusCode, "Internal Server Error", "text/plain"))
+                responder.complete(stringResponse(statusCode, "Internal Server Error", TEXT_PLAIN))
             }
         }
     }
@@ -253,7 +292,7 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable {
 
   ////////////// helpers //////////////
 
-  val defaultHeaders = List(HttpHeader("Content-Type", "text/plain"))
+  final val defaultHeaders = List(HttpHeader("Content-Type", TEXT_PLAIN))
 
   lazy val serverActor = Actor.registry.actorsFor("spray-can-server").head
 
index fad277b..fc796c8 100644 (file)
@@ -51,16 +51,21 @@ object RESTPaths {
   final val ResourcesPolicyYAML = "/resources/policy\\.yaml".r
 
   final val RolesAgreementsMap = "/resources/roles-agreements\\.map".r
+
+  final val ResourceEventPath = "/rcevent/([^/]+)/?".r
+
+  final val IMEventPath = "/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
+  final val UserBalancePath = "/user/([^/]+)/balance/?".r
 
   /**
    * Use this URI path to query for the user state.
    */
-  final val UserStatePath = "/user/(.+)/state/?".r
+  final val UserStatePath = "/user/([^/]+)/state/?".r
 
   /**
    * Use this administrative URI path to ping all services used by Aquarium.
index 95d04fe..7326267 100644 (file)
@@ -150,7 +150,8 @@ class UserActor extends ReflectiveRoleableActor {
       )
     }
 
-    DEBUG("New %s = %s", shortNameOfType[IMStateSnapshot], this._imState)
+    DEBUG("Initial %s = %s", shortNameOfType[IMStateSnapshot], this._imState.toJsonString)
+    logSeparator()
   }
 
   /**
@@ -211,7 +212,8 @@ class UserActor extends ReflectiveRoleableActor {
     loadUserStateAndUpdateRoleHistory()
 
     if(haveUserState) {
-      DEBUG("%s = %s", shortNameOfType[UserState], this._userState)
+      DEBUG("Initial %s = %s", shortNameOfType[UserState], this._userState.toJsonString)
+      logSeparator()
     }
   }
 
@@ -278,7 +280,7 @@ class UserActor extends ReflectiveRoleableActor {
       }
     }
 
-    DEBUG("New %s = %s", shortNameOfType[IMStateSnapshot], this._imState)
+    DEBUG("Updated %s = %s", shortNameOfType[IMStateSnapshot], this._imState.toJsonString)
     logSeparator()
   }
 
@@ -313,6 +315,8 @@ class UserActor extends ReflectiveRoleableActor {
       this._latestResourceEventOccurredMillis = event.rcEvent.occurredMillis
 
       DEBUG("Below threshold (%s ms). Not processing %s", this._timestampTheshold, rcEvent.toJsonString)
+      logSeparator()
+
       return
     }
 
@@ -333,7 +337,7 @@ class UserActor extends ReflectiveRoleableActor {
     val calculationReason = RealtimeBillingCalculation(None, now)
     val eventOccurredMillis = rcEvent.occurredMillis
 
-    DEBUG("Using %s", currentResourcesMap)
+//    DEBUG("Using %s", currentResourcesMap.toJsonString)
 
     this._userState = aquarium.userStateComputations.doMonthBillingUpTo(
       billingMonthInfo,
@@ -346,7 +350,8 @@ class UserActor extends ReflectiveRoleableActor {
 
     this._latestResourceEventOccurredMillis = event.rcEvent.occurredMillis
 
-    DEBUG("New %s = %s", shortClassNameOf(this._userState), this._userState)
+    DEBUG("Updated %s = %s", shortClassNameOf(this._userState), this._userState.toJsonString)
+    logSeparator()
   }
 
   def onGetUserBalanceRequest(event: GetUserBalanceRequest): Unit = {
index ca613d1..d9b85ab 100644 (file)
@@ -150,7 +150,9 @@ final class UserStateComputations(_aquarium: => Aquarium) extends Loggable {
           // ZERO, we are OK!
           case 0 ⇒
             // NOTE: Keep the caller's calculation reason
-            latestUserState.newWithChangeReason(calculationReason)
+            val result = latestUserState.newWithChangeReason(calculationReason)
+            clog.end()
+            result
 
           // We had more, so must recompute
           case n if n > 0 ⇒
index 1495857..b4d3193 100644 (file)
@@ -41,6 +41,7 @@ import gr.grnet.aquarium.event.model.im.IMEventModel
 import gr.grnet.aquarium.util.shortClassNameOf
 import gr.grnet.aquarium.util.date.MutableDateCalc
 import gr.grnet.aquarium.computation.parts.RoleHistory
+import gr.grnet.aquarium.util.json.JsonSupport
 
 /**
  *
@@ -67,7 +68,7 @@ case class IMStateSnapshot(
      * This is the recorded role history
      */
     roleHistory: RoleHistory
-) {
+) extends JsonSupport {
 
   /**
    * True iff the user has ever been activated even once.
index 94de94c..00fe6ed 100644 (file)
@@ -35,6 +35,8 @@
 
 package gr.grnet.aquarium.logic.accounting.dsl
 
+import gr.grnet.aquarium.util.json.JsonSupport
+
 
 /**
  * Enumerates known resources by name.
@@ -45,7 +47,7 @@ package gr.grnet.aquarium.logic.accounting.dsl
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 
-final class DSLResourcesMap(underlying: Map[String, DSLResource]) {
+final case class DSLResourcesMap(underlying: Map[String, DSLResource]) extends JsonSupport {
   def this(list: List[DSLResource]) = this(Map(list.map(r ⇒ (r.name, r)): _*))
 
   def map   = underlying
index 04ecf59..458e5fe 100644 (file)
@@ -236,8 +236,6 @@ class MongoDBStore(
       add(UserState.JsonNames.theFullBillingMonth_month, bmi.month).
       get()
 
-    logger.debug("findLatestUserStateForFullMonthBilling(%s, %s) query: %s".format(userID, bmi, query))
-
     // Descending order, so that the latest comes first
     val sorter = new BasicDBObject(UserState.JsonNames.occurredMillis, -1)