You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@esme.apache.org by rh...@apache.org on 2009/11/09 10:13:14 UTC
svn commit: r834002 - in
/incubator/esme/trunk/server/src/main/scala/org/apache/esme/api: API2.scala
ApiHelper.scala
Author: rhirsch
Date: Mon Nov 9 09:13:10 2009
New Revision: 834002
URL: http://svn.apache.org/viewvc?rev=834002&view=rev
Log:
[ESME-14] Redesign, rework, write unit tests for, and fully document API
Patch from Ethan Jewett applied
Added:
incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/ApiHelper.scala
Modified:
incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/API2.scala
Modified: incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/API2.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/API2.scala?rev=834002&r1=834001&r2=834002&view=diff
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/API2.scala (original)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/API2.scala Mon Nov 9 09:13:10 2009
@@ -18,8 +18,8 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-/*
+
+ /*
* API2.scala
*
* To change this template, choose Tools | Template Manager
@@ -41,77 +41,81 @@
import model._
import org.apache.esme.actor._
-import scala.xml.{NodeSeq, Text, Elem, XML}
+import scala.xml.{NodeSeq, Text, Elem, XML, Node}
import scala.collection.mutable.ListBuffer
import java.util.logging._
-object API2 extends XMLApiHelper {
+object API2 extends ApiHelper {
val logger: Logger = Logger.getLogger("org.apache.esme.api")
def dispatch: LiftRules.DispatchPF = {
- case Req("api2" :: "session" :: Nil, _, GetRequest) => status // No params
- case Req("api2" :: "session" :: Nil, _, PostRequest) => login // token
- case Req("api2" :: "session" :: Nil, _, DeleteRequest) => logout // No params
+ case Req("api2" :: "session" :: Nil, _, GetRequest) => allSessions
+ case Req("api2" :: "session" :: Nil, _, PostRequest) => addSession
+ case Req("api2" :: "session" :: Nil, _, DeleteRequest) => removeSession
- case Req("api2" :: "users" :: Nil, _, GetRequest) => allUsers _ // No params
+ case Req("api2" :: "users" :: Nil, _, GetRequest) => allUsers
// Add a method to get detail for a specific user
- case Req("api2" :: "user" :: "messages" :: Nil, _, GetRequest) => allUserMsgs // tag (opt)
+// Document the fact that tag is no longer a parameter here
+ case Req("api2" :: "user" :: "messages" :: Nil, _, GetRequest) => allUserMsgs
+// Document the new method for getting messages belonging to a particular tag
+ case Req("api2" :: "user" :: "messages" :: "tag" :: tag :: Nil, _, GetRequest)
+ => () => allUserMsgs(tag)
// Possibly deprecate and move to api2/messages or api2/pools/poolName/messages
- case Req("api2" :: "user" :: "messages" :: Nil, _, PostRequest) => () => addMsg // message,
-// via (opt), pool (opt), realm (opt), metadata (opt), tags (opt), replyto (opt)
+// Add back long-poll option
+ case Req("api2" :: "user" :: "messages" :: Nil, _, PostRequest) => () => addMsg
- case Req("api2" :: "user" :: "followees" :: Nil, _, GetRequest) => allFollowees // No params
- case Req("api2" :: "user" :: "followees" :: Nil, _, PostRequest) => addFollowee // userId
+ case Req("api2" :: "user" :: "followees" :: Nil, _, GetRequest) => allFollowees
+ case Req("api2" :: "user" :: "followees" :: Nil, _, PostRequest) => addFollowee
case Req("api2" :: "user" :: "followees" :: userId :: Nil, _, DeleteRequest)
- => removeFollow(Box(List(userId))) // No params
+ => removeFollow(Box(List(userId)))
- case Req("api2" :: "user" :: "followers" :: Nil, _, GetRequest) => allFollowers // No params
+ case Req("api2" :: "user" :: "followers" :: Nil, _, GetRequest) => allFollowers
- case Req("api2" :: "user" :: "tracks" :: Nil, _, GetRequest) => allTracking // No params
- case Req("api2" :: "user" :: "tracks" :: Nil, _, PostRequest) => addTracking // track (regex)
+ case Req("api2" :: "user" :: "tracks" :: Nil, _, GetRequest) => allTracking
+ case Req("api2" :: "user" :: "tracks" :: Nil, _, PostRequest) => addTracking
// Add a method to get detail for a specific track (or messages for the track?)
case Req("api2" :: "user" :: "tracks" :: trackId :: Nil, _, DeleteRequest) => ()
- => removeTracking(Box(List(trackId))) // No params
+ => removeTracking(Box(List(trackId)))
- case Req("api2" :: "user" :: "actions" :: Nil, _, GetRequest) => allActions // No params
- case Req("api2" :: "user" :: "actions" :: Nil, _, PostRequest) => addAction // name, test, action
+ case Req("api2" :: "user" :: "actions" :: Nil, _, GetRequest) => allActions
+ case Req("api2" :: "user" :: "actions" :: Nil, _, PostRequest) => addAction
// Add a method to get detail of a specific action
case Req("api2" :: "user" :: "actions" :: actionId :: Nil, _, PutRequest) => ()
- => changeAction(Box(List(actionId))) // enabled (boolean)
+ => changeAction(Box(List(actionId)))
case Req("api2" :: "user" :: "actions" :: actionId :: Nil, _, DeleteRequest) => ()
- => removeAction(Box(List(actionId))) // No params
+ => removeAction(Box(List(actionId)))
- case Req("api2" :: "pools" :: Nil, _, GetRequest) => allPools // No params
- case Req("api2" :: "pools" :: Nil, _, PostRequest) => () => addPool // poolName
+ case Req("api2" :: "pools" :: Nil, _, GetRequest) => allPools
+ case Req("api2" :: "pools" :: Nil, _, PostRequest) => () => addPool
// Add a method to delete pool
// Add a method to get the detail for a pool
// Add a method to get the list of users in a pool
case Req("api2" :: "pools" :: poolId :: "users" :: Nil, _, PostRequest) => ()
- => addUserToPool(Box(List(poolId))) // realm, userId, permission
+ => addUserToPool(Box(List(poolId)))
// Add a method to delete a user from a pool
// Add a method to get the messages from a pool
// Add a method to post a new message to a pool
// Add a method to get list of conversations
case Req("api2" :: "conversations" :: conversationId :: Nil, _, GetRequest) => ()
- => getConversation(Box(List(conversationId))) // No params
-// Add a method to post a message to a conversation??
-
-// Do we need this? - specifically, can we merge it with the /api2/user/messages
-// resource with a different match?
-// case Req("api2" :: "wait_for_msgs" :: Nil, _, GetRequest) => waitForMsgs
+ => getConversation(Box(List(conversationId)))
+// Add a method to post a message to a conversation??
}
- def status(): LiftResponse =
- {
- val ret: Box[NodeSeq] = User.currentUser.map(_.toXml)
- ret
+ def allSessions(): LiftResponse = {
+ val r: Box[NodeSeq] =
+ for (user <- User.currentUser ?~ S.?("base_rest_api_err_not_logged_in"))
+ yield {
+ <session>{userToXml(user)}</session>
+ }
+
+ r
}
- def login(): LiftResponse = {
- val res: Box[Boolean] = if (User.loggedIn_?) Empty else
+ def addSession(): LiftResponse = {
+ val r: Box[NodeSeq] = if (User.loggedIn_?) Empty else
for (token <- S.param("token") ?~ S.?("base_rest_api_err_missing_param", "token");
auth <- AuthToken.find(By(AuthToken.uniqueId, token))
?~ "Token not found";
@@ -121,36 +125,52 @@
User.logUserIn(user)
val myActor = buildActor(user.id)
restActor(Full(myActor))
- true
+ <session>{userToXml(user)}</session>
}
- res
+ r
}
- def logout(): LiftResponse = {
+ def removeSession(): LiftResponse = {
User.logUserOut()
true
}
- def allUsers(): LiftResponse =
- for (user <- User.findAll) yield user.toXml
+ def allUsers(): LiftResponse = {
+ val users: NodeSeq = for (user <- User.findAll) yield userToXml(user)
+ val r: Elem = <users>{users}</users>
+ r
+ }
def allUserMsgs(): LiftResponse = {
- val t: Box[NodeSeq] =
- for (tagName <- S.param("tag");
- tag <- Tag.find(By(Tag.name, tagName)))
- yield tag.findMessages.map(_.toXml)
-
- val r: Box[NodeSeq] =
- t or (for (user <- calcUser ?~ S.?("base_rest_api_err_param_not_found", "User");
- val lst = Mailbox.mostRecentMessagesFor(user.id, 40))
- yield lst.flatMap{ case (msg, why, _) => msg.toXml % why.attr})
+ val r: Box[Node] =
+ for (user <- calcUser ?~ S.?("base_rest_api_err_param_not_found", "User");
+ val lst = Mailbox.mostRecentMessagesFor(user.id, 40))
+ yield {
+ <messages>{lst.flatMap{ case (msg, why, _) => msg.toXml % why.attr}}</messages>
+ }
r
+ }
+
+ def allUserMsgs(tag: String): LiftResponse = {
+ val r: Box[Node] =
+ for (tagName <- Box(List(tag));
+ tag <- Tag.find(By(Tag.name, tagName)))
+ yield {
+ <tag>
+ <name>{tag.name}</name>
+ <messages>{tag.findMessages.map(_.toXml)}</messages>
+ </tag>
+ }
+
+ r
}
def addMsg(): LiftResponse = {
+// Should return the created message
+
val r: Box[Boolean] =
for (user <- calcUser.map(_.id.is) ?~ S.?("base_rest_api_err_param_not_found", "User");
msg <- S.param("message") ?~ S.?("base_rest_api_err_missing_param", "message"))
@@ -180,20 +200,24 @@
def allFollowees(): LiftResponse = {
- val r: Box[NodeSeq] = for (user <- calcUser) yield
- user.following().map(_.toXml)
-
- r
+ val followees: NodeSeq = calcUser.map(_.following)
+ .map(_.map(userToXml(_)))
+ .openOr(<no_followees/>)
+
+ <followees>{followees}</followees>
}
def addFollowee(): LiftResponse = {
- val r: Box[Boolean] =
+ val r: Box[Node] =
for (user <- User.currentUser;
userName <- S.param("userId");
other <- User.findFromWeb(userName)
- ) yield user.follow(other)
+ ) yield {
+ user.follow(other)
+ userToXml(other)
+ }
- r
+ r
}
@@ -208,17 +232,19 @@
}
def allFollowers(): LiftResponse = {
- val r: Box[NodeSeq] = for (user <- calcUser) yield
- user.followers().map(_.toXml)
-
- r
+ val followers: NodeSeq = calcUser.map(_.followers)
+ .map(_.map(userToXml(_)))
+ .openOr(<no_followers/>)
+
+ <followers>{followers}</followers>
}
def allTracking(): LiftResponse = {
- val ret: Box[NodeSeq] =
+ val tracks: Box[NodeSeq] =
for (user <- User.currentUser ?~ S.?("base_rest_api_err_not_logged_in"))
yield Tracking.findAll(By(Tracking.user, user)).flatMap(_.toXml)
- ret
+
+ <tracks>{tracks}</tracks>
}
def addTracking(): LiftResponse = {
@@ -333,7 +359,7 @@
id <- conversationId.map(toLong) ?~ S.?("base_rest_api_err_missing_param", "id")
) yield <conversation id={id.toString}>{
Message.findAndPrime(By(Message.conversation, id),
- OrderBy(Message.id, Ascending)).map(_.toXml)
+ OrderBy(Message.id, Ascending)).map(_.toXml)
}</conversation>
ret
@@ -349,30 +375,15 @@
private def calcUser: Box[User] =
S.param("user").flatMap(User.findFromWeb) or
- User.currentUser
-
+ User.currentUser
+ def createTag(in: NodeSeq) = <api_response>{in}</api_response>
- def waitForMsgs(): LiftResponse = {
- val future = new LAFuture[List[(Message, MailboxReason)]]()
-
- def waitForAnswer: Box[List[(Message, MailboxReason)]] =
- future.get(6L * 60L * 1000L)
-
- var r: Box[NodeSeq] =
- for (act <- restActor.is ?~ "No REST actor";
- val ignore = act ! ListenFor(future, 5 minutes);
- answer <- waitForAnswer ?~ "Didn't get an answer")
- yield answer.flatMap{ case (msg, reason) => msg.toXml % reason.attr}
-
- r
- }
-
- def createTag(in: NodeSeq) = <esme_api>{in}</esme_api>
-
+ private def userToXml(user: User): Elem =
+ <user><id>{user.id.toString}</id><nickname>{user.niceName}</nickname><image>{user.image}</image><whole_name>{user.wholeName}</whole_name></user>
private def buildActor(userId: Long): RestActor = {
- val ret = new RestActor
+ val ret = new RestActor
ret ! StartUp(userId)
ret
}
@@ -382,12 +393,12 @@
}
- class RestActor extends LiftActor {
+ class RestActor extends LiftActor {
private var userId: Long = _
private var msgs: List[(Message, MailboxReason)] = Nil
private var listener: Box[LAFuture[List[(Message, MailboxReason)]]] = Empty
- protected def messageHandler = {
+ protected def messageHandler = {
case StartUp(userId) =>
this.userId = userId
Distributor ! Distributor.Listen(userId, this)
@@ -417,7 +428,7 @@
case xs =>
who.satisfy(xs)
- msgs = Nil
+ msgs = Nil
listener = Empty
}
}
@@ -428,7 +439,7 @@
private case object ByeBye
private case class ListenFor(who: LAFuture[List[(Message, MailboxReason)]],
howLong: TimeSpan)
- private case object ReleaseListener
+ private case object ReleaseListener
}
// TODO:
@@ -436,3 +447,6 @@
// 2. Fix errors so that they properly indicate a missing parameter or 404
// 3. Change changeAction so that if the "enabled" parameter doesn't show up it will simply use
// the current value for the action, not throw an error.
+// 4. Match based on the return content type header to determine what to return (default to XML)
+// 5. Re-enable streaming message API (using comet approach)
+
Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/ApiHelper.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/ApiHelper.scala?rev=834002&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/ApiHelper.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/ApiHelper.scala Mon Nov 9 09:13:10 2009
@@ -0,0 +1,92 @@
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * ApiHelper.scala
+ *
+ * To change this template, choose Tools | Template Manager
+ * and open the template in the editor.
+ */
+
+/*
+ * This code stolen shamelessly and modified. Based on the
+ * XMLApiHelper.scala in Liftweb 1.0
+ */
+
+package org.apache.esme.api
+
+import net.liftweb._
+import http._
+import util._
+import common._
+import Helpers._
+
+import scala.xml.{NodeSeq, Text, Elem, UnprefixedAttribute, Null, Node}
+
+trait ApiHelper {
+ implicit def boolToResponse(in: Boolean): LiftResponse =
+ buildResponse(in, Empty, Text(""))
+
+ implicit def canBoolToResponse(in: Box[Boolean]): LiftResponse =
+ buildResponse(in openOr false, in match {
+ case Failure(msg, _, _) => Full(Text(msg))
+ case _ => Empty
+ }, Text(""))
+
+ implicit def pairToResponse(in: (Boolean, String)): LiftResponse =
+ buildResponse(in._1, Full(Text(in._2)), Text(""))
+
+ implicit def nodeSeqToResponse(in: NodeSeq): LiftResponse =
+ buildResponse(true, Empty, in)
+
+ implicit def listElemToResponse(in: Seq[Node]): LiftResponse =
+ buildResponse(true, Empty, in)
+
+ implicit def elemToResponse(in: Elem): LiftResponse =
+ buildResponseFromElem(true, Empty, in)
+
+ implicit def canNodeToResponse(in: Box[NodeSeq]): LiftResponse = in match {
+ case Full(n) => buildResponse(true, Empty, n)
+ case Failure(msg, _, _) => buildResponse(false, Full(Text(msg)), Text(""))
+ case _ => buildResponse(false, Empty, Text(""))
+ }
+
+ implicit def putResponseInBox(in: LiftResponse): Box[LiftResponse] = Full(in)
+
+ implicit def takeResponseOutOfBox(in: Box[LiftResponse]): LiftResponse =
+ in openOr false
+
+ /**
+ * The method that wraps the outer-most tag around the body
+ */
+ def createTag(in: NodeSeq): Elem
+
+ /**
+ * Build the Response based on the body
+ */
+ protected def buildResponse(success: Boolean, msg: Box[NodeSeq],
+ body: NodeSeq): LiftResponse =
+ XmlResponse(createTag(body))
+
+ protected def buildResponseFromElem(success: Boolean, msg: Box[NodeSeq], body: Elem): LiftResponse = {
+ XmlResponse(createTag(body))
+ }
+}