[ESME-328] Fix resend and various other stuff. About ready to merge.

git-svn-id: https://svn.apache.org/repos/asf/esme/branches/esme-328_comet_timeline_rewrite@1090382 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index b16c7b6..a02f9fc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,7 +75,7 @@
     </developer>

     </developers>

     <properties>

-        <lift.version>2.2</lift.version>

+        <lift.version>2.3</lift.version>

         <scala.version>2.8.1</scala.version>

         <compass.version>2.1.1</compass.version>

         <lucene.version>2.4.0</lucene.version>

diff --git a/project/build/EsmeProject.scala b/project/build/EsmeProject.scala
index 940e205..171c926 100644
--- a/project/build/EsmeProject.scala
+++ b/project/build/EsmeProject.scala
@@ -1,7 +1,7 @@
 import sbt._
 
 class EsmeProject(info: ProjectInfo) extends DefaultWebProject(info) {
-  val liftVersion = "2.2"
+  val liftVersion = "2.3"
   val compassVersion = "2.1.1"
   val luceneVersion = "2.4.0"
 
diff --git a/src/main/scala/bootstrap/liftweb/Boot.scala b/src/main/scala/bootstrap/liftweb/Boot.scala
index 0e164d9..a2ed26e 100644
--- a/src/main/scala/bootstrap/liftweb/Boot.scala
+++ b/src/main/scala/bootstrap/liftweb/Boot.scala
@@ -197,7 +197,7 @@
 
     LiftRules.setSiteMap(SiteMap(entries: _*))
 
-    S.addAround(ExtSession.requestLoans)          
+    LiftRules.earlyInStateful.append(ExtSession.testCookieEarlyInStateful)
 
     // API security rules
     LiftRules.dispatch.append(API2.authorizationRules)
diff --git a/src/main/scala/org/apache/esme/comet/StreamTimeline.scala b/src/main/scala/org/apache/esme/comet/StreamTimeline.scala
index c66bb0f..79fb69a 100644
--- a/src/main/scala/org/apache/esme/comet/StreamTimeline.scala
+++ b/src/main/scala/org/apache/esme/comet/StreamTimeline.scala
@@ -62,9 +62,8 @@
         case _ => currentPool = StreamTimeline.PublicPool    
       }
                                              
-      if(filterPool) {                    
-        messages = getMessages 
-        clearMessages = true                       
+      if(filterPool) {                     
+        messages = getMessages                      
         this ! ForceRender
       }                    
     }
@@ -76,30 +75,26 @@
       }
       
       if(filterResender) {
-        messages = getMessages  
-        clearMessages = true                   
+        messages = getMessages                  
         this ! ForceRender
       }                    
     }
     
     case FilterPool(on) => {
       filterPool = on 
-      messages = getMessages    
-      clearMessages = true                   
+      messages = getMessages                   
       this ! ForceRender
     }
     
     case FilterResender(on) => {
       filterResender = on
-      messages = getMessages      
-      clearMessages = true              
+      messages = getMessages              
       this ! ForceRender
     }                     
   }    
   
   override def lowPriority = {
-    case ForceRender =>      
-// TODO: Adapt to new timeline format
+    case ForceRender =>                 
       reRender(true)
 
     case Distributor.NewMessage(msg) =>     
@@ -122,8 +117,7 @@
           scheduled = true    
           ActorPing.schedule(this, ForceRender, 30000L)
         }
-      }  
-// TODO: Adapt to new timeline format
+      }                                
       else reRender(true)
   }
   
diff --git a/src/main/scala/org/apache/esme/comet/Timeline.scala b/src/main/scala/org/apache/esme/comet/Timeline.scala
index 850f4a7..feea553 100644
--- a/src/main/scala/org/apache/esme/comet/Timeline.scala
+++ b/src/main/scala/org/apache/esme/comet/Timeline.scala
@@ -23,11 +23,12 @@
 import net.liftweb.util._
 import net.liftweb.common._
 import net.liftweb.util.Helpers._
-import scala.xml._
+import scala.xml._ 
 import js._
-import JsCmds._
+import JsCmds._             
 import JE._      
-import net.liftweb.http.js.jquery.JqJsCmds.{PrependHtml} 
+import net.liftweb.http.js.jquery.JqJsCmds.{PrependHtml,FadeOut}  
+import net.liftweb.http.SHtml.{BasicElemAttr}
 
 import org.apache.esme._
 import org.apache.esme.actor._
@@ -39,7 +40,6 @@
 trait Timeline extends CometActor {   
                                                                            
   protected var messages: List[(Long,MailboxReason,Boolean)] = Nil  
-  protected var clearMessages: Boolean = false  
   protected val jsId: String  
   
   override def defaultPrefix = Full("timeline")
@@ -53,71 +53,19 @@
   }
   
   def render = { 
-  
-// TODO - handle clearMessages = true.   
-// TODO - Get resend working    
-// TODO - need to escape the replyHref string so that messages with ? in them don't bomb
-  
+                                        
     val msgMap = Message.findMessages(messages map {_._1})
     val toDisplay = for ((id, reason, resent) <- messages;
                          msg <- msgMap.get(id))
                     yield (msg, reason, resent)    
                     
     <div id={jsId}>{toDisplay.map(renderMessage(_))}</div>
-  }  
-  
-  protected def renderMessage(m: (Message,MailboxReason,Boolean)) = {
-    val imageUrl = m._1.author.obj.map(_.image_url).openOr("")       
-    val authorNickname = m._1.author.obj.map(_.niceName).openOr("")
-    val messageId = "message_" + m._1.id.is.toString
-    val messageBody = m._1.digestedXHTML
-    val messagePool:String = m._1.pool.obj.map("in pool \'" + _.getName + "\'").openOr("")  
-    val replyHref = "javascript:setReplyTo(" + m._1.id.is.toString + ", '"+ messageBody + "', " + m._1.pool.obj.map(_.id.is).openOr(0) + ", '" + authorNickname + "')" 
-                              
-    val convId = m._1.conversation.is  
-    val convHref = LiftRules.context.path + "/conversation/" + convId
-    val convTransform:CssBindFunc = if(convId != 0) {
-      ".conversation [href]" #> convHref
-    } else {
-      ".conversation" #> Text("")
-    }   
-                        
-    val authorHref = LiftRules.context.path + "/user/" + authorNickname
-    
-// TODO: Put date in the "ago" format
-    val messageDateStr = toInternetDate(m._1.when)
-    val messageReason = if(m._2.attr.length > 0){
-      if(m._2.attr.key == "resent_from") {
-        "resent by " + User.find(m._2.attr.value).map(_.nickname).openOr("")
-      } else {
-        "caused by " + m._2.attr.key
-      }
-    } else {
-      "via " + m._1.source
-    }
-      
-    val suppString = messagePool + " " + messageDateStr + " " + messageReason
-  
-    ("#avatar [src]" #> imageUrl &
-     ".updates-box [id]" #> messageId &
-     ".msgbody *" #> messageBody &
-     ".supp_data *" #> suppString &
-     ".reply [href]" #> replyHref &
-     convTransform &       
-     ".author [href]" #> authorHref &
-     ".author *" #> authorNickname )(messageTemplate)
   }                                                 
   
   // If we need to filter out some messages on display, override this method.
   def filter(msgs:List[(Message,MailboxReason,Boolean)]):List[(Message,MailboxReason,Boolean)] = {
     msgs 
   }     
-  
-  protected def prependMessage(m:Message, r:MailboxReason, rs:Boolean) {     
-    val newMessage = renderMessage((m,r,rs))    
-    val update = PrependHtml(jsId, newMessage)
-    partialUpdate(update)
-  }
 
 // TODO Should be factored out into a template  
   val messageTemplate = 
@@ -130,7 +78,7 @@
     		<div class="msgbody"/>
       	<div class="supp_data"/>				
     		<div class="actions">
-    			<a href="#"  class="resend">
+    			<a href="#"  class="resend resend_link">
     				<lift:loc>ui_messages_message_label_resend</lift:loc>
     			</a><span class="resend">| </span>
     			<a href="#" class="reply">
@@ -142,5 +90,79 @@
     			</a>
     		</div>
     	</div>
-    </div>   
+    </div>      
+    
+  protected def prependMessage(m:Message, r:MailboxReason, rs:Boolean) {     
+    val newMessage = renderMessage((m,r,rs))    
+    val update = PrependHtml(jsId, newMessage)
+    partialUpdate(update)
+  }       
+
+  private def resendMessage(m:Message):JsCmd = {
+    for (user <- User.currentUser;
+         msgId = m.id.is) {
+           Distributor ! Distributor.ResendMessage(user.id, msgId)       
+    }        
+
+    val resendId = "resend_" + m.id.toString
+
+    FadeOut(resendId,0,200)
+  }   
+    
+  protected def renderMessage(m: (Message,MailboxReason,Boolean)) = {
+    val imageUrl = m._1.author.obj.map(_.image_url).openOr("")       
+    val authorNickname = m._1.author.obj.map(_.niceName).openOr("")
+    val messageId = "message_" + m._1.id.is.toString
+    val messageBody = m._1.digestedXHTML  
+    val replyBody = m._1.body.replaceAll("\\'","\\\\\\'")
+    val messagePool:String = m._1.pool.obj.map("in pool \'" + _.getName + "\'").openOr("")  
+    val replyHref = "javascript:setReplyTo(" + m._1.id.is.toString + ", '"+ replyBody + "', " + m._1.pool.obj.map(_.id.is).openOr(0) + ", '" + authorNickname + "')"             
+
+    val convId = m._1.conversation.is  
+    val convHref = LiftRules.context.path + "/conversation/" + convId
+    val convTransform:CssBindFunc = if(convId != 0) {
+      ".conversation [href]" #> convHref
+    } else {
+      ".conversation" #> Text("")
+    }       
+
+    val resendId = "resend_" + m._1.id.toString 
+    val resendAttrs = BasicElemAttr("id",resendId).compose(BasicElemAttr("class","resend"))
+
+    val resendTransform:CssBindFunc = 
+      if(m._3 || m._1.author.is == User.currentUser.map(_.id.is).openOr(0)) {
+        ".resend" #> Text("")
+      } else {  
+        ".resend_link" #> SHtml.a(
+          () => resendMessage(m._1), 
+          S.loc("ui_messages_message_label_resend").openOr(Text("")),
+          resendAttrs)
+      } 
+
+    val authorHref = LiftRules.context.path + "/user/" + authorNickname
+
+// TODO: Put date in the "ago" format
+    val messageDateStr = toInternetDate(m._1.when)
+    val messageReason = if(m._2.attr.length > 0){
+      if(m._2.attr.key == "resent_from") {
+        "resent by " + User.find(m._2.attr.value).map(_.nickname).openOr("")
+      } else {
+        "caused by " + m._2.attr.key
+      }
+    } else {
+      "via " + m._1.source
+    }
+
+    val suppString = messagePool + " " + messageDateStr + " " + messageReason
+
+    ("#avatar [src]" #> imageUrl &
+     ".updates-box [id]" #> messageId &
+     ".msgbody *" #> messageBody &
+     ".supp_data *" #> suppString &
+     ".reply [href]" #> replyHref &
+     convTransform &       
+     ".author [href]" #> authorHref &
+     ".author *" #> authorNickname &
+     resendTransform )(messageTemplate)
+  }   
 }
diff --git a/src/main/scala/org/apache/esme/model/ExtSession.scala b/src/main/scala/org/apache/esme/model/ExtSession.scala
index 6d28fb0..9744f2e 100644
--- a/src/main/scala/org/apache/esme/model/ExtSession.scala
+++ b/src/main/scala/org/apache/esme/model/ExtSession.scala
@@ -24,7 +24,7 @@
 import util._
 import common._
 
-object ExtSession extends ExtSession with MetaProtoExtendedSession[ExtSession] {
+object ExtSession extends ExtSession with LongKeyedMetaMapper[ExtSession] with MetaProtoExtendedSession[ExtSession] {
   override def dbTableName = "ext_session" // define the DB table name
 
   def logUserIdIn(uid: String): Unit = User.logUserIdIn(uid)
@@ -34,7 +34,7 @@
   type UserType = User
 }
 
-class ExtSession extends ProtoExtendedSession[ExtSession] {
+class ExtSession extends LongKeyedMapper[ExtSession] with ProtoExtendedSession[ExtSession] {
   def getSingleton = ExtSession // what's the "meta" server
 
 }
diff --git a/src/main/scala/org/apache/esme/model/Mailbox.scala b/src/main/scala/org/apache/esme/model/Mailbox.scala
index db4bcf9..ada8edb 100644
--- a/src/main/scala/org/apache/esme/model/Mailbox.scala
+++ b/src/main/scala/org/apache/esme/model/Mailbox.scala
@@ -107,7 +107,7 @@
   def attr = new UnprefixedAttribute("tag", tagName, Null)
 }            
 case class ConvFollowReason(convId: Long) extends MailboxReason {
-  def attr = new UnprefixedAttribute("conversation", convId, Null)
+  def attr = new UnprefixedAttribute("conversation", convId.toString, Null)
 }
 case class LoginReason(userId: Long) extends MailboxReason {
   def attr = new UnprefixedAttribute("login", userId.toString, Null)
diff --git a/src/main/scala/org/apache/esme/snippet/UserSnip.scala b/src/main/scala/org/apache/esme/snippet/UserSnip.scala
index 04f1a7b..e33e14b 100644
--- a/src/main/scala/org/apache/esme/snippet/UserSnip.scala
+++ b/src/main/scala/org/apache/esme/snippet/UserSnip.scala
@@ -79,21 +79,6 @@
   }
 }
 
-object JsonResender extends JsonHandler{
-  def apply(in: Any): JsCmd = in match {
-    case JsonCmd("resend", _, map: Map[String, Any], _) =>
-      for (msgId <- map.get("msg_id").map(toLong);
-           user  <- User.currentUser)
-             Distributor ! Distributor.ResendMessage(user.id, msgId)
-    
-      Noop
-
-    case _ => Noop
-  }
-}
-
-
-
 class UserSnip extends DispatchSnippet {
 
   def dispatch: DispatchIt = 
@@ -104,8 +89,7 @@
       "following" -> following _,
       "loginForm" -> loginForm _,
       "loggedIn" -> loggedInFilter _,
-      "accessPools" -> accessPools _,
-      "resendScript" -> resendScript _,
+      "accessPools" -> accessPools _,  
       "popular" -> popular _,
       "links" -> links _)
 
@@ -149,8 +133,6 @@
   
 
    // Image of user as part of an img tag
-  def logoutimage: MetaData = ("src" -> (Message.root + "/images/btn-signout.gif"))
-  def searchimage: MetaData = ("src" -> (Message.root + "/images/btn-search.gif"))
   
   def image: MetaData = ("src" -> (User.currentUser.map(_.image_url)openOr "/images/avatar.jpg"))
   
@@ -200,16 +182,6 @@
     }
   </xml:group>
   
-  def resendScript(in: NodeSeq): NodeSeq = 
-  <xml:group>
-    {Script(JsonResender.jsCmd)}
-    {Script(Function("resend_msg", List("msg_id"),
-                     JsonResender.call("resend",
-                                        JsObj("msg_id" -> JsVar("msg_id")))
-        ))
-    }
-  </xml:group>
-  
   def popular(in: NodeSeq): NodeSeq = 
     PopStatsActor !? PopStatsActor.TopStats(ResendStat, 5, 1 week) match {
       case l: List[Tuple2[Long,Int]] =>
diff --git a/src/main/webapp/templates-hidden/base.html b/src/main/webapp/templates-hidden/base.html
index c0d8498..a5b6fe3 100644
--- a/src/main/webapp/templates-hidden/base.html
+++ b/src/main/webapp/templates-hidden/base.html
@@ -32,14 +32,14 @@
         <meta name="generator" content="" lang="en-US" />

         <meta name="author" content="ESME Designed by Joy Reyes" lang="en-US" />

         <meta name="Robots" content="index,follow,noodp" />

-        <link rel="stylesheet" href="../style/style.css" type="text/css" media="screen" />

-        <link rel="stylesheet" href="../style/tipTip.css" type="text/css" media="screen" />

+        <link rel="stylesheet" href="/style/style.css" type="text/css" media="screen" />

+        <link rel="stylesheet" href="/style/tipTip.css" type="text/css" media="screen" />

         <script id="jquery" src="/classpath/jquery.js" type="text/javascript"/>

         <script id="json" src="/classpath/json.js" type="text/javascript"/>

-        <script src="../scripts/jquery.TipTip.js" type="text/javascript"/> 

-        <script src="../scripts/esme_tool_tip.js" type="text/javascript"/> 

-        <script src="../scripts/jquery.validate.js" type="text/javascript"/> 

-        <script src="../scripts/esme_validate.js" type="text/javascript"/> 

+        <script src="/scripts/jquery.TipTip.js" type="text/javascript"/> 

+        <script src="/scripts/esme_tool_tip.js" type="text/javascript"/> 

+        <script src="/scripts/jquery.validate.js" type="text/javascript"/> 

+        <script src="/scripts/esme_validate.js" type="text/javascript"/> 

     </head>

     <body id="back">

      <div id="messages" class="esme_message_bar" style="display: none"></div>

@@ -63,7 +63,7 @@
                         </ul>

 

                         <lift:Menu.item name="logout">

-                           <img lift:snippet="UserSnip.logoutimage" alt="" class="tipelement" title="Log out of ESME." />

+                           <img src="/images/btn-signout.gif" alt="" class="tipelement" title="Log out of ESME." />

                          </lift:Menu.item>

                     </div>

                 </div><!--// ENDS HEADER -->

@@ -87,7 +87,7 @@
                                     <input type="text" class="inputBox required alphanumeric nowhitespace" name="term" value=""/>

                                 </div>

                                 <div class="searchButton">

-                                    <input type="image" onclick="javascript:form.submit();" lift:snippet="UserSnip.searchimage" style="padding:0;"/>

+                                    <input type="image" onclick="javascript:form.submit();" src="/images/btn-search.gif" style="padding:0;"/>

                                 </div>

                             </div>

                         </form><!--search form--><h3 class ="tipelement" title="These are tags that appear often in your timeline."><lift:loc>ui_messages_message_label_tag_cloud</lift:loc></h3>