You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@esme.apache.org by dp...@apache.org on 2009/01/23 01:26:47 UTC

svn commit: r736864 [2/3] - in /incubator/esme/trunk/server: ./ src/main/scala/bootstrap/liftweb/ src/main/scala/org/ src/main/scala/org/apache/ src/main/scala/org/apache/esme/ src/main/scala/org/apache/esme/actor/ src/main/scala/org/apache/esme/api/ s...

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/MsgParser.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/MsgParser.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/MsgParser.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/MsgParser.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,368 @@
+/**
+ * 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.
+ */
+
+package org.apache.esme.lib
+
+
+import scala.util.parsing.combinator.{Parsers, ImplicitConversions}
+import scala.util.matching._
+import net.liftweb._
+import util._
+import mapper._
+import Helpers._
+import net.liftweb.util.{Failure => BoxFailure}
+import scala.util.parsing.input.Reader
+import scala.xml.{XML, NodeSeq, Node, Text}
+import org.apache.esme._
+import model._
+
+object MsgParser extends Parsers with ImplicitConversions with CombParserHelpers {
+  def parseMessage(in: String): Box[List[MsgInfo]] = begin(in) match {
+    case Success(xs, _) => Full(xs)
+    case _ => Empty
+  }
+
+  lazy val begin: Parser[List[MsgInfo]] = 
+  startSpace ~> rep1(url | atName | hashTag | text) <~ spaceEOF
+
+  lazy val startSpace = rep(' ')
+
+  lazy val url: Parser[URL] = httpUrl ^^ {url => URL(UrlStore.make(url))}
+
+  lazy val atNameStr: Parser[String] = alpha ~ rep(alpha | digit | '_') ^^ {
+    case first ~ more => first + more.mkString
+  }
+  
+  lazy val atName: Parser[MsgInfo] = '@' ~> atNameStr ~ rep('.' ~> atNameStr) ^^ {
+    case name ~ domainlist => 
+      val nickName: String = name
+      val wholeName: String = (name :: domainlist).mkString(".")
+      User.find(By(User.nickname, nickName)) match {
+        case Full(u) => AtName(u)
+        case _ => MsgText("@"+wholeName)
+      }
+  }
+  
+  // def ip_schemepart = (accept("//") ~> login) ~> opt( '/' ~> urlpath)
+
+  lazy val login: Parser[String] = userPass ^^ {
+    case ("", _) => ""
+    case (user, "") => user + "@"
+    case (user, password) => user + ":" + password + "@"
+  }
+  
+  lazy val userPass: Parser[(String, String)] =
+  opt(user ~ opt( ':' ~>  password ) <~ '@' ) ^^ {
+    case None => ("", "")
+    case Some(user ~ pwd) => (user, pwd.getOrElse(""))
+  }
+
+  lazy val hostport: Parser[String] = host ~ opt( ':' ~> port ) ^^ {
+    case host ~ port => host + (port.map(p => ":"+p).getOrElse(""))
+  }
+  
+  lazy val host: Parser[String] = hostname | hostnumber
+
+  lazy val hostname: Parser[String] = rep( domainlabel <~ '.' ) ~ toplabel ^^ {
+    case lst ~ end => (lst ::: List(end)).mkString(".")
+  }
+
+  lazy val domainlabel: Parser[String] = 
+  (alphadigit ~ rep(alphadigit | '-') /*~ alphadigit */ ^^ {
+      case d ~ dl /* ~ d3*/ => d + dl.mkString /*+ d3*/}) |
+  (alphadigit ^^ {_.toString})
+
+  lazy val toplabel: Parser[String] = 
+  (alpha ~ rep(alphadigit | '-' ) /* ~ alphadigit */ ^^ {
+      case a ~ al /* ~ a3*/ => a + al.mkString // + a3
+    })  | (alpha ^^ {_.toString})
+
+  lazy val alphadigit: Parser[Elem] = alpha | digit
+
+  lazy val hostnumber: Parser[String] = 
+  digits ~ '.' ~ digits ~ '.' ~ digits ~ '.' ~ digits ^^ {
+    case one ~ _ ~ two ~ _ ~ three ~ _ ~ four =>
+      one + "." + two + "." + three + "." + four
+  }
+  
+  lazy val port: Parser[String] = digits 
+  lazy val user: Parser[String] = rep( uchar | ';' | '?' | '&' | '=' ) ^^ {_.mkString}
+  lazy val password: Parser[String] = user
+
+  lazy val mailtoUrl: Parser[String] = accept("mailto:") ~> emailAddr
+
+  lazy val emailAddr: Parser[String] = rep1(xchar) ^^ {
+    case xs => xs.mkString
+  }
+
+  lazy val scheme: Parser[String] = (accept("http://") | accept("https://")) ^^ {_ mkString}
+
+  lazy val httpUrl: Parser[String] = scheme ~ login ~ urlpart ^^ {
+    case front ~ login ~ urlpart => front + login + urlpart
+  }
+
+  lazy val urlpart: Parser[String] = 
+  hostport ~ opt( '/' ~> hpath ~ opt('?' ~> search )) ^^ {
+    case hp ~ None => hp
+    case hp ~ Some(pth ~ None) => hp + "/" + pth
+    case hp ~ Some(pth ~ Some(search)) =>
+      hp + "/" + pth + "?" + search
+  }
+
+  lazy val hpath: Parser[String] = hsegment ~ rep('/' ~> hsegment) ^^ {
+    case x ~ xs => (x :: xs).mkString("/")
+  }
+
+
+  lazy val hsegment: Parser[String] = rep(uchar | ';' | ':' | '@' | '&' | '=') ^^
+  {_.mkString}
+
+
+  lazy val search: Parser[String] = rep(uchar | ';' | ':' | '@' | '&' | '=' | '/') ^^ {
+    _.mkString
+  }
+
+  lazy val lowAlpha: Parser[Elem] = elem("Low Alpha", c => (c >= 'a' && c <= 'z'))
+
+  lazy val hiAlpha: Parser[Elem] = elem("High Alpha", c => (c >= 'A' && c <= 'Z'))
+  
+  lazy val safe: Parser[Elem] = elem("Safe", c => c == '$' || c == '-' || c == '_' || c == '.' ||
+                                     c == '+')
+  lazy val reserved: Parser[Elem] = elem("Safe", c => c == ';' || c == '/' ||
+                                         c == '?' || c == ':' ||
+                                         c == '@' ||
+                                         c == '&' ||  c == '=')
+
+  lazy val extra: Parser[Elem] = elem("Extra", c => c == '!' || c == '*' || c == '\'' ||
+                                      c == '(' || c == ')' || c == ',')
+
+  lazy val hex: Parser[Elem] = elem("Hex", c => (c >= '0' && c <= '9') ||
+                                    (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+
+  lazy val escape: Parser[Elem] = '%' ~> hex ~ hex ^^ {
+    case high ~ low => Integer.parseInt(high.toString + low.toString, 16).toChar
+  }
+  
+  lazy val alpha: Parser[Elem] = lowAlpha | hiAlpha
+  lazy val unreserved: Parser[Elem] = alpha | digit | safe | extra
+  lazy val uchar: Parser[Elem] = unreserved | escape
+  lazy val xchar: Parser[Elem] = unreserved | reserved | escape
+
+  lazy val digits: Parser[String] = rep1(digit) ^^ {_.mkString}
+  
+  lazy val nameChar: Parser[Elem] = elem("Name Char", isNameChar _)
+  
+  def isNameChar(in: Char): Boolean = isTagChar(in) 
+
+  lazy val hashTag: Parser[HashTag] = '#' ~> rep1(tagChar) ^^ {
+    case xs => HashTag(Tag.findOrCreate(xs))
+  }
+  
+  lazy val tagChar: Parser[Elem] = elem("Tag Char", isTagChar _)
+  
+  def isTagChar(in: Char): Boolean = Character.isLetter(in) ||
+  Character.isDigit(in) || (in == '_')
+
+  lazy val spaceEOF = rep(' ') ~ EOF
+
+  lazy val text: Parser[MsgText] = rep1(not(spaceEOF) ~ not(url) ~ not(atName) ~
+                                        not(hashTag) ~> anyChar) ^^ {
+    case xs => MsgText(xs.mkString(""))
+  }
+
+  lazy val EOF: Parser[Elem] = elem("EOF", isEof _)
+
+  def perform(in: String): Box[Performances] = _perform(in) match {
+    case Success(p, _) => Full(p)
+    case _ => Empty
+  }
+
+  lazy val _perform: Parser[Performances] =
+  (acceptCI("filter") ~ lineSpace ~ EOF ^^^ PerformFilter) |
+  (acceptCI("resend") ~ lineSpace ~ EOF ^^^ PerformResend) |
+  (mailtoUrl ~ opt(rep(EOL) ~> rep1(anyChar)) <~ EOF ^^ {
+    case mt ~ text => MailTo(mt, text.map(_ mkString))
+  }) |
+  (scheme ~ userPass ~ urlpart ~ rep(httpHeader) ~ httpData <~ EOF ^^ {
+      case protocol ~ userPass ~ urlpart ~ hdrs ~ data =>
+        HttpTo(protocol + urlpart, userPass._1, userPass._2, hdrs, data)
+    }) |
+  (acceptCI("atom:") ~> httpUrl <~ EOF ^^ {url => FetchAtom(UrlStore.make(url))}) |
+  (acceptCI("rss:") ~> httpUrl <~ EOF ^^ {url => FetchRss(UrlStore.make(url))})
+
+  lazy val httpHeader: Parser[(String, String)] = EOL ~ accept("header:") ~
+  lineSpace ~> rep1(uchar) ~ '=' ~ rep1(uchar) ^^ {
+    case name ~ _ ~ value => (name.mkString, value.mkString)
+  }
+
+  lazy val httpData: Parser[Option[String]] = opt(EOL ~> rep1(anyChar)) ^^ { _ map(_ mkString) }
+
+  def testMessage(in: String): Box[TestAction] = _testMessage(in) match {
+    case Success(ta, _) => Full(ta)
+    case _ => Empty
+  }
+  
+  lazy val _testMessage: Parser[TestAction] = testExpr
+
+  lazy val testExpr: Parser[TestAction] = phrase(_testExpr)
+
+  lazy val _testExpr: Parser[TestAction] = 
+  testFactor*(orOp ^^^ {(l, r) => OrAction(l, r)} | andOp ^^^ {(l,r) => AndAction(l, r)})
+
+  lazy val orOp: Parser[String] = whiteSpace ~ '|' ~ whiteSpace ^^^ "|"
+
+    lazy val andOp: Parser[String] = whiteSpace ~ '&' ~ whiteSpace ^^^ "&"
+  
+  lazy val testFactor: Parser[TestAction] = (notTest |
+  testAt | testRegex | testString |
+  testTag | 
+  testParen | testPercent |
+  testDates | testLogin |
+  testFollowed | testUnfollowed |
+  testProfile | testRegular |
+  anyMsg | testToMe) <~ whiteSpace
+
+  lazy val toOpr: Parser[EqOprType] =
+  ('=' ^^^ EqOpr) | (accept("<>") ^^^ NeOpr)
+
+  lazy val anyOpr: Parser[OprType] =
+  accept(">=") ^^^ GeOpr |
+  accept("<=") ^^^ LeOpr |
+  accept("<>") ^^^ NeOpr |
+  accept("<") ^^^ LtOpr |
+  accept(">") ^^^ GtOpr |
+  accept("=") ^^^ EqOpr
+
+  lazy val dateKeyword: Parser[DateType] = 
+  acceptCI("day") ^^^ DayDateType | 
+  acceptCI("date") ^^^ DateDateType |
+  acceptCI("hour") ^^^ HourDateType  |
+  acceptCI("month") ^^^ MonthDateType | 
+  acceptCI("minute") ^^^ MinuteDateType
+
+  lazy val number: Parser[Int] = rep1(digit) ^^ {case x => x.mkString.toInt}
+
+  lazy val numberList: Parser[List[Int]] =
+  whiteSpace ~ '(' ~ whiteSpace ~> number ~ rep(whiteSpace ~ ',' ~ whiteSpace ~> number) <~
+  whiteSpace ~ ')' ~ whiteSpace ^^ {
+    case x ~ xs => x :: xs
+  }
+
+  lazy val testLogin: Parser[TestAction] = acceptCI("login") ^^^ LoginAction()
+
+  lazy val testFollowed: Parser[TestAction] = acceptCI("followed") ^^^ FollowedAction()
+
+  lazy val testUnfollowed: Parser[TestAction] = acceptCI("unfollowed") ^^^ UnfollowedAction()
+
+  lazy val testProfile: Parser[TestAction] = acceptCI("profile") ^^^ ProfileAction()
+
+  lazy val testRegular: Parser[TestAction] = acceptCI("every") ~ whiteSpace ~> number <~ whiteSpace ~ acceptCI("mins") ^^ {
+    case mins => RegularAction(mins)
+  }
+
+  lazy val testDates: Parser[TestAction] =
+  ((whiteSpace ~> dateKeyword) ~ (whiteSpace ~> toOpr) ~ (whiteSpace ~> numberList) ^^
+   {
+      case kw ~ opr ~ lst => DateTestAction(kw, opr, lst)
+    }) | ((whiteSpace ~> dateKeyword) ~ (whiteSpace ~> anyOpr) ~ (whiteSpace ~> number) ^^
+          {
+      case kw ~ opr ~ num => DateTestAction(kw, opr, List(num))
+    })
+
+
+  lazy val toPeople: Parser[List[AtUserAction]] = 
+  (whiteSpace ~ '(' ~ whiteSpace ~> testAt ~ 
+   rep(whiteSpace ~ ',' ~ whiteSpace ~> testAt) <~ whiteSpace ~ ')' ~ whiteSpace ^^
+   {
+      case f ~ lst => f :: lst
+    }
+  ) | testAt ^^ {case ta => List(ta)}
+
+  lazy val testTo: Parser[TestAction] =
+  whiteSpace ~ acceptCI("to") ~ whiteSpace ~> toOpr ~ whiteSpace ~ toPeople ^^ {
+    case opr ~ _ ~ who => AtSendAction(who.map(_.userId), opr)
+  }
+
+  lazy val testPercent: Parser[TestAction] =
+  whiteSpace ~> digit ~ opt(digit) <~ '%' ~ whiteSpace ^^ {
+    case d ~ d2 => val str = d.toString + (d2.map(_.toString).getOrElse(""))
+      PercentAction(str.toInt)
+  }
+
+  lazy val testParen: Parser[TestAction] =
+  whiteSpace ~ '(' ~ whiteSpace ~> _testExpr <~ whiteSpace ~ ')' ~ whiteSpace ^^ {
+    case x => ParenAction(x)
+  }
+
+  lazy val testAt: Parser[AtUserAction] =
+  (whiteSpace ~ '@' ~> rep1(digit) <~ whiteSpace ^^ {case dig => AtUserAction(dig.mkString.toLong)}) |
+  (atName ^^ {
+      case AtName(user) => AtUserAction(user.id)
+    })
+
+  lazy val reChars: Parser[Char] = (('\\' ~ '/') ^^^ '/') |
+  (not('/') ~> anyChar)
+  
+  lazy val testRegex: Parser[TestAction] = 
+  whiteSpace ~ '/' ~> rep1(reChars) <~ '/' ~ whiteSpace ^^ {
+    case re if validRegex(re.mkString).isDefined => RegexAction(re.mkString)
+  }
+  
+  lazy val strChars: Parser[Char] = (('\\' ~ '"') ^^^ '/') |
+  (not('"') ~> anyChar)
+  
+  lazy val testString: Parser[TestAction] =
+  whiteSpace ~ '"' ~> rep1(strChars) <~ '"' ~ whiteSpace ^^ {
+    case re => StringAction(re.mkString)
+  }
+
+  def validRegex(in: String): Box[Regex] = tryo(in.r)
+
+  lazy val testTag: Parser[TestAction] = whiteSpace ~ '#' ~> rep1(tagChar) <~ whiteSpace ^^ {
+    case xs => HashAction(Tag.findOrCreate(xs.mkString).id, xs.mkString)
+  }
+
+  lazy val testOr: Parser[TestAction] = 
+  (testExpr <~ whiteSpace ~ '|' ~ whiteSpace) ~ testExpr ^^ {
+    case left ~ right => OrAction(left, right)
+  }
+
+  lazy val testAnd: Parser[TestAction] = 
+  (testExpr <~ whiteSpace ~ '&' ~ whiteSpace) ~ testExpr ^^ {
+    case left ~ right => AndAction(left, right)
+  }
+  
+  lazy val notTest: Parser[TestAction] = whiteSpace ~ acceptCI("not(") ~> testExpr <~ whiteSpace ~ ')' ^^ {
+    case x => NotAction(x)
+  }
+  
+  lazy val anyMsg: Parser[TestAction] = 
+  whiteSpace ~ acceptCI("any") ~ whiteSpace ^^^ AnyAction
+  
+  lazy val testToMe: Parser[TestAction] = whiteSpace ~ acceptCI("tome") ~ whiteSpace ^^^ SentToMeAction
+
+}
+
+sealed trait MsgInfo
+case class MsgText(text: String) extends MsgInfo
+case class AtName(user: User) extends MsgInfo
+case class HashTag(tag: Tag) extends MsgInfo
+case class URL(url: UrlStore) extends MsgInfo

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/TagUtils.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/TagUtils.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/TagUtils.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/TagUtils.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,52 @@
+/**
+ * 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.
+ */
+
+package org.apache.esme.lib
+
+object TagUtils {
+
+  // Normalize the frequencies from arbitrary Integers to the range (0.0 - 1.0)
+  def normalize(llsi: List[(String,Int)]):List[(String,Float)] = {
+    val maxVal: Float = llsi.foldLeft(0)(_ max _._2).toFloat
+    
+    llsi.map{case (name, value) => (name, value.toFloat / maxVal)}
+  }
+  
+  // Compounds a bunch of (String, Int) elements so that [(String1, Int1), (String1, Int2)] becomes [(String1, Int1+Int2)]
+  def compound(llsi: List[(String,Int)]): List[(String,Int)] =
+    llsi.foldLeft[Map[String, Int]](Map.empty) {
+      case (map, (str, cnt)) => map + (str -> (map.getOrElse(str, 0) + cnt))
+    }.toList
+
+  
+  def everyEven(x:List[(String, Int)]):List[(String, Int)] = everyOther(x)
+  def everyOdd(x:List[(String, Int)]):List[(String, Int)] = everyOther(dummy::x)
+  
+  private val dummy = ("", 0)
+  private def everyOther(x:List[(String, Int)]):List[(String, Int)] = {
+    x match {
+      case Nil => Nil
+      case _ :: Nil => Nil
+      case _ :: elem :: sublist => elem :: everyOther(sublist)
+    }
+  }
+
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/.keep
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/.keep?rev=736864&view=auto
==============================================================================
    (empty)

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Action.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Action.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Action.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Action.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,444 @@
+package org.apache.esme.model
+
+/**
+ * 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.
+ */
+
+import net.liftweb._
+import mapper._
+import http._
+import util._
+
+import org.apache.esme._
+import lib._
+import actor._
+import external._
+
+import java.util.Calendar
+import scala.xml.{Text, Node, Elem => XmlElem}
+
+object Action extends Action with LongKeyedMetaMapper[Action] {
+  override def afterCommit = notifyDistributor _ :: super.afterCommit
+
+  private def notifyDistributor(in: Action) {
+    Distributor ! Distributor.UpdateTrackingFor(in.user, 
+                                                Distributor.PerformTrackingType)
+  }
+
+  override def afterSave = startStopActors _ :: super.afterSave
+  
+  private def startStopActors(in: Action)  {
+    if (!in.removed.is && in.enabled) {
+      in.startActors()
+    } else {
+      SchedulerActor ! SchedulerActor.StopRegular(in.id)
+      MessagePullActor ! MessagePullActor.StopPullActor(in.id)
+    }
+  }
+  
+  type TestFunc = (Message, Long, Calendar, MailboxReason) => Boolean
+  
+  lazy val TrueFunc: TestFunc = {case _ => true}
+  lazy val SentToMe: TestFunc = (m, u, c, r) => m.sentToIds.contains(u)
+  
+  def toFunc(in: TestAction): TestFunc = in match {
+    case AnyAction => TrueFunc
+
+    case NotAction(action) =>
+      val f: TestFunc = this.toFunc(action)
+      (m, u, c, r) => !f(m,u,c,r)
+
+    case OrAction(left, right) =>
+      val f1 = toFunc(left)
+      val f2 = toFunc(right)
+      (m, u, c, r) => f1(m, u, c, r) || f2(m, u, c, r)
+  
+    case AndAction(left, right) =>
+      val f1 = toFunc(left)
+      val f2 = toFunc(right)
+      (m, u, c, r) => f1(m, u, c, r) && f2(m, u, c, r)
+  
+    case LoginAction() =>
+      (m, u, c, r) => r.isInstanceOf[LoginReason]
+
+    case FollowedAction() =>
+      (m, u, c, r) => r.isInstanceOf[FollowedReason]
+
+    case UnfollowedAction() =>
+      (m, u, c, r) => r.isInstanceOf[UnfollowedReason]
+      
+    case RegularAction(mins) =>
+      (m, u, c, r) => r.isInstanceOf[RegularReason]
+      
+    case ProfileAction() =>
+      (m, u, c, r) => r.isInstanceOf[ProfileReason]
+      
+    case AtUserAction(userId) =>
+      (m, u, c, r) => m.author.is == userId 
+        
+    case SentToMeAction =>
+      SentToMe
+      
+    case RegexAction(re) =>
+      val r = re.r
+      (m, u, c, reason) => r.findFirstIn(m.getText).isDefined
+        
+    case StringAction(s) =>
+      val str = s.toLowerCase.trim
+      (m, u, c, r) => m.getText.toLowerCase.indexOf(str) >= 0
+        
+    case HashAction(id, _) =>
+      (m, u, c, r) => m.tagIds.contains(id)
+        
+    case ParenAction(a) =>
+      toFunc(a)
+        
+    case PercentAction(percent) =>
+      (m, u, c, r) => Helpers.randomInt(100) <= percent
+      
+    case  AtSendAction(users, EqOpr) =>
+      (m, u, c, r) => !m.sentToIds.intersect(users).isEmpty
+
+    case  AtSendAction(users, NeOpr) =>
+      (m, u, c, r) => m.sentToIds.intersect(users).isEmpty
+      
+    case DateTestAction(dt, ot, what) =>
+      (m, u, c, r) => ot.buildFunc(dt.buildFunc(c), what)
+  }
+  
+  def regularActions(in: TestAction): List[RegularAction] = in match {
+    case NotAction(a) => regularActions(a)
+
+    case ParenAction(a) => regularActions(a)
+    
+    case OrAction(left, right) => regularActions(left) ::: regularActions(right)
+  
+    case AndAction(left, right) => regularActions(left) ::: regularActions(right)
+
+    case a @ RegularAction(mins) => List(a)
+        
+    case _ => Nil
+  }
+}
+
+class Action extends LongKeyedMapper[Action] {
+
+  def startActors() {
+    for(regular <- regularActions) regular match { 
+      case RegularAction(mins) => SchedulerActor ! SchedulerActor.StartRegular(this, mins * 60)
+    }
+    val urlSourcePrefix = "url:"
+    theAction.actionFunc match {
+      case a @ (FetchFeed(url)) => {
+        User.find(user) match {
+          case Full(u) =>
+            val msgList = Message.findAll(By(Message.source, urlSourcePrefix + url.uniqueId),
+                                          OrderBy(Message.id, Descending),
+                                          MaxRows(1))
+            val lastMsg = if (msgList.isEmpty) None 
+              else {
+                val m = msgList.first
+                Some(Distributor.UserCreatedMessage(user, m.getText, m.tags, m.when, Empty, m.source, Full(m.replyTo)))
+              }
+
+            val feed = a match {
+              case FetchAtom(_) => new AtomFeed(u, url.url, urlSourcePrefix + url.uniqueId, 0, Nil)
+              case FetchRss(_) => new RssFeed(u, url.url, urlSourcePrefix + url.uniqueId, 0, Nil)
+            }
+            MessagePullActor ! MessagePullActor.StartPullActor(id, lastMsg, feed)
+        }
+      }
+      case _ =>
+    }
+  }
+
+  def getSingleton = Action // what's the "meta" server
+  def primaryKeyField = id
+
+  object id extends MappedLongIndex(this)
+
+  object user extends MappedLongForeignKey(this, User)
+  object name extends MappedPoliteString(this, 64) {
+    
+    override def validations =
+    valMinLen(2, "The name must be at least 2 characters long") _ :: super.validations
+    
+  }
+  private[model] object theAction extends MappedText(this) {
+    import MsgParser._
+    
+    override def validations = checkParsing _ :: super.validations
+    def checkParsing(in: String): List[FieldError] = _perform(in) match {
+      case Failure(msg, _) => List(FieldError(this, Text(msg)))
+      case Error(msg, _) => List(FieldError(this, Text(msg)))
+      case _ => Nil
+    }
+    
+    def actionFunc = _perform(is) match {
+      case Success(v, _) => v
+    }
+  }
+  
+  private[model] object theTest extends MappedText(this) {
+    import MsgParser._
+    
+    override def validations = checkParsing _ :: super.validations
+    def checkParsing(in: String): List[FieldError] = testExpr(in) match {
+      case Failure(msg, _) => List(FieldError(this, Text(msg)))
+      case Error(msg, _) => List(FieldError(this, Text(msg)))
+      case _ => Nil
+    }
+
+    def testFunc = testExpr(is) match {
+      case Success(v, _) => Action.toFunc(v)
+    }
+  }
+  
+  object uniqueId extends MappedUniqueId(this, 32) {
+    override def dbIndexed_? = true
+  }
+  object removed extends MappedBoolean(this) {
+    override def defaultValue = false
+  }
+  object disabled extends MappedBoolean(this) {
+    override def defaultValue = false
+  }
+  import MsgParser._
+
+  def setTest(in: String): Box[Action] = testExpr(in) match {
+    case Success(v, _) => Full(this.theTest(v.toStr))
+    case Failure(m, _) => net.liftweb.util.Failure(m, Empty, Empty)
+    case Error(m, _) => net.liftweb.util.Failure(m, Empty, Empty)
+  }
+
+  def testText = theTest.is
+
+  def regularActions: List[RegularAction] = testExpr(testText) match {
+    case Success(v, _) => Action.regularActions(v)
+  }
+
+  def actionText = theAction.is
+  
+  def setAction(in: String): Box[Action] = _perform(in) match {
+    case Success(_, _) => Full(this.theAction(in))
+    case Failure(m, _) => net.liftweb.util.Failure(m, Empty, Empty)
+    case Error(m, _) => net.liftweb.util.Failure(m, Empty, Empty)
+  }
+
+
+
+  def matcher: PerformMatcher = {
+    new PerformMatcher(theTest.testFunc, id, uniqueId,
+                       theAction.actionFunc)
+  }
+
+  def enabled: Boolean = !disabled.is
+
+  override def toXml: XmlElem =
+  <action id={id.toString}
+    name={name.is}
+    test={theTest.is}
+    action={theAction.is}
+    enabled={enabled.toString}></action>
+
+}
+
+class PerformMatcher(val func: Action.TestFunc, val performId: Long,
+                     val uniqueId: String, val whatToDo: Performances) {
+  def doesMatch(msg: Message, userId: Long, cal: Calendar, reason: MailboxReason): Boolean =
+  func(msg, userId, cal, reason)
+
+  def filter_? = whatToDo == PerformFilter
+}
+
+
+
+sealed trait TestAction {
+  def toStr: String
+}
+case object AnyAction extends TestAction {
+  def toStr = "any"
+}
+case object SentToMeAction extends TestAction {
+  def toStr = "tome"
+}
+case class NotAction(action: TestAction) extends TestAction {
+  def toStr = "not( "+action.toStr+" )"
+}
+case class OrAction(left: TestAction, right: TestAction) extends TestAction {
+  def toStr = left.toStr + " | " + right.toStr
+}
+
+case class AndAction(left: TestAction, right: TestAction) extends TestAction {
+  def toStr = left.toStr + " &  " + right.toStr
+}
+
+case class AtUserAction(userId: Long) extends TestAction {
+  def toStr = "@"+userId
+}
+
+case class RegexAction(re: String) extends TestAction {
+  def toStr = "/"+fix+"/"
+  
+  def fix: String = {
+    val ret = new StringBuilder
+    var pos = 0
+    val len = re.length
+    while (pos < len) {
+      re.charAt(pos) match {
+        case '/' => ret.append("\\/")
+        case c => ret.append(c)
+      }
+      pos += 1
+    }
+    ret.toString
+  }
+}
+
+case class StringAction(re: String) extends TestAction {
+  def toStr = "\""+fix+"\""
+  
+  def fix: String = {
+    val ret = new StringBuilder
+    var pos = 0
+    val len = re.length
+    while (pos < len) {
+      re.charAt(pos) match {
+        case '"' => ret.append("\\\"")
+        case c => ret.append(c)
+      }
+      pos += 1
+    }
+    ret.toString
+  }
+}
+
+case class HashAction(hashId: Long, str: String) extends TestAction {
+  def toStr = "#"+str
+}
+
+case class ParenAction(action: TestAction) extends TestAction {
+  def toStr = "( "+action.toStr+" )"
+}
+
+case class PercentAction(percent: Int) extends TestAction {
+  def toStr = percent+"% "
+}
+
+case class AtSendAction(users: List[Long], opr: EqOprType) extends TestAction {
+  def toStr = "to "+opr.toStr+" "+(users match {
+      case x :: Nil => "@"+x
+      case xs => xs.map(v => "@"+v).mkString("(", ", ", ")")
+    })
+}
+
+case class LoginAction extends TestAction {
+  def toStr = "login"
+}
+
+case class FollowedAction extends TestAction {
+  def toStr = "followed"
+}
+
+case class UnfollowedAction extends TestAction {
+  def toStr = "unfollowed"
+}
+
+case class ProfileAction extends TestAction {
+  def toStr = "profile"
+}
+
+case class RegularAction(mins: Int) extends TestAction {
+  def toStr = "every " + mins + " mins"
+}
+
+case class DateTestAction(dateType: DateType, opt: OprType, what: List[Int]) extends TestAction {
+  def toStr = dateType.toStr + " " + opt.toStr + " " + (
+    what match {
+      case x :: Nil => x.toString
+      case xs => xs.mkString("(", ", ", ")")
+    }
+  )
+}
+
+sealed trait OprType {
+  def buildFunc: (Int, List[Int]) => Boolean
+  def toStr: String
+}
+
+sealed trait EqOprType extends OprType
+case object EqOpr extends EqOprType {
+  val buildFunc: (Int, List[Int]) => Boolean = (v, l) => l.forall(_ == v)
+  def toStr: String = "="
+}
+case object NeOpr extends EqOprType {
+  val buildFunc: (Int, List[Int]) => Boolean = (v, l) => l.forall(_ != v)
+  def toStr: String = "<>"
+}
+case object GeOpr extends OprType {
+  val buildFunc: (Int, List[Int]) => Boolean = (v, l) => l.forall(_ >= v)
+  def toStr: String = ">="
+}
+case object GtOpr extends OprType {
+  val buildFunc: (Int, List[Int]) => Boolean = (v, l) => l.forall(_ > v)
+  def toStr: String = ">"
+}
+case object LeOpr extends OprType {
+  val buildFunc: (Int, List[Int]) => Boolean = (v, l) => l.forall(_ <= v)
+  def toStr: String = "<="
+}
+case object LtOpr extends OprType {
+  val buildFunc: (Int, List[Int]) => Boolean = (v, l) => l.forall(_ < v)
+  def toStr: String = "<"
+}
+
+sealed trait DateType {
+  def buildFunc: Calendar => Int
+  def toStr: String
+}
+case object DayDateType extends DateType {
+  val buildFunc: Calendar => Int = c => c.get(Calendar.DAY_OF_WEEK)
+  def toStr: String = "day"
+}
+case object DateDateType extends DateType {
+  val buildFunc: Calendar => Int = c => c.get(Calendar.DAY_OF_MONTH)
+  def toStr: String = "date"
+}
+case object MonthDateType extends DateType {
+  val buildFunc: Calendar => Int = c => c.get(Calendar.MONTH)
+  def toStr: String = "month"
+}
+case object HourDateType extends DateType {
+  val buildFunc: Calendar => Int = c => c.get(Calendar.HOUR_OF_DAY)
+  def toStr: String = "hour"
+}
+case object MinuteDateType extends DateType {
+  val buildFunc: Calendar => Int = c => c.get(Calendar.MINUTE)
+  def toStr: String = "minute"
+}
+
+sealed trait Performances
+case class MailTo(who: String, text: Option[String]) extends Performances
+case class HttpTo(url: String, user: String, password: String, headers: List[(String, String)], data: Option[String]) extends Performances
+case class FetchFeed(url: UrlStore) extends Performances
+case class FetchAtom(override val url: UrlStore) extends FetchFeed(url)
+case class FetchRss(override val url: UrlStore) extends FetchFeed(url)
+case object PerformResend extends Performances
+case object PerformFilter extends Performances

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/AuthToken.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/AuthToken.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/AuthToken.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/AuthToken.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,62 @@
+package org.apache.esme.model
+
+/**
+ * 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.
+ */
+
+import net.liftweb._
+import mapper._
+import util._
+
+object AuthToken extends AuthToken with LongKeyedMetaMapper[AuthToken] {
+  // override def dbIndexes = Index(user, status) :: super.dbIndexes
+}
+
+class AuthToken extends LongKeyedMapper[AuthToken] {
+  def getSingleton = AuthToken // what's the "meta" server
+  def primaryKeyField = id
+
+  object id extends MappedLongIndex(this)
+  object user extends MappedLongForeignKey(this, User)
+  object description extends MappedPoliteString(this, 64)
+  
+  object uniqueId extends MappedUniqueId(this, 32) {
+    override def dbIndexed_? = true
+  }
+/*
+
+  object url extends MappedString(this, 256)
+  object headerName extends MappedString(this, 64)
+  object headerValue extends MappedString(this, 256)
+  object status extends MappedEnum(this, TokenStates) {
+    override def defaultValue = TokenStates.Normal
+  }*/
+}
+
+/*
+object TokenStates extends Enumeration {
+  val Normal = Value(0, "Normal")
+  val Send = Value(1, "Send")
+  val Removed = Value(2, "Removed")
+  val Disabled = Value(3, "Disabled")
+
+  val forPopup = List(Normal, Send, Disabled)
+}
+*/

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/DidPerform.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/DidPerform.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/DidPerform.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/DidPerform.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,44 @@
+package org.apache.esme.model
+
+/**
+ * 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.
+ */
+
+import net.liftweb._
+import mapper._
+import util._
+
+import scala.xml._
+
+object DidPerform extends DidPerform with LongKeyedMetaMapper[DidPerform] {
+
+}
+
+class DidPerform extends LongKeyedMapper[DidPerform] {
+  def getSingleton = DidPerform // what's the "meta" server
+  def primaryKeyField = id
+
+  object id extends MappedLongIndex(this)
+  object what extends MappedLongForeignKey(this, Action)
+  object message extends MappedLongForeignKey(this, Message)
+  object when extends MappedDateTime(this) {
+    override def defaultValue = Helpers.timeNow
+  }
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/ExtSession.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/ExtSession.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/ExtSession.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/ExtSession.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,41 @@
+package org.apache.esme.model
+/**
+ * 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.
+ */
+
+
+import net.liftweb._
+import mapper._
+import util._
+
+object ExtSession extends ExtSession with MetaProtoExtendedSession[ExtSession] {
+  override def dbTableName = "ext_session" // define the DB table name
+
+  def logUserIdIn(uid: String): Unit = User.logUserIdIn(uid)
+
+  def recoverUserId: Box[String] = User.currentUserId
+
+  type UserType = User
+}
+
+class ExtSession extends ProtoExtendedSession[ExtSession] {
+  def getSingleton = ExtSession // what's the "meta" server
+
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Group.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Group.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Group.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Group.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,49 @@
+package org.apache.esme.model
+
+/**
+ * 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.
+ */
+
+import net.liftweb._
+import mapper._
+import util._
+
+object Group extends Group with LongKeyedMetaMapper[Group] {
+  override def dbTableName = "a_group" // define the DB table name
+
+  def findGroup(name: String): Box[Group] = find(By(this.name, name))
+}
+
+class Group extends LongKeyedMapper[Group] {
+  def getSingleton = Group // what's the "meta" server
+  def primaryKeyField = id
+
+  object id extends MappedLongIndex(this)
+
+  object name extends MappedPoliteString(this, 256) {
+    override def validations =
+    this.valMinLen(3, "The minimum group length is 3 characters") _ ::
+    super.validations
+
+  override def setFilter = trim _ :: Helpers.capify _ :: super.setFilter
+
+  override def dbIndexed_? = true
+  }
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Mailbox.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Mailbox.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Mailbox.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Mailbox.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,102 @@
+package org.apache.esme.model
+
+/**
+ * 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.
+ */
+
+import net.liftweb._
+import mapper._
+import util._
+
+import scala.xml._
+
+object Mailbox extends Mailbox with LongKeyedMetaMapper[Mailbox] {
+  override def dbTableName = "mailbox" // define the DB table name
+
+  def mostRecentMessagesFor(userId: Long, cnt: Int):
+  List[(Message, MailboxReason)] = {
+    val mb = findAll(By(user, userId), OrderBy(id, Descending),
+                     MaxRows(cnt))
+
+    val msgToFind: List[Long] = mb.map(_.message.is)
+
+    val map = Message.findMessages(msgToFind)
+
+    mb.flatMap(m => map.get(m.message).map(msg => (msg, m.reason)))
+  }
+    
+  override def dbIndexes = Index(user, message) :: super.dbIndexes
+}
+
+class Mailbox extends LongKeyedMapper[Mailbox] {
+  def getSingleton = Mailbox // what's the "meta" server
+  def primaryKeyField = id
+
+  object id extends MappedLongIndex(this)
+  object user extends MappedLongForeignKey(this, User)
+  object message extends MappedLongForeignKey(this, Message)
+  object viaTrack extends MappedLongForeignKey(this, Tracking)
+  object directlyFrom extends MappedLongForeignKey(this, User)
+  object conversation extends MappedLongForeignKey(this, Message)
+  object resentBy extends MappedLongForeignKey(this, User)
+  object login extends MappedLongForeignKey(this, User)
+  object followed extends MappedLongForeignKey(this, User)
+  object unfollowed extends MappedLongForeignKey(this, User)
+  object profile extends MappedLongForeignKey(this, User)
+  object regular extends MappedLongForeignKey(this, Action)
+
+  lazy val reason: MailboxReason =
+  viaTrack.can.map(TrackReason) or directlyFrom.can.map(DirectReason)  or
+  conversation.can.map(ConversationReason) openOr NoReason
+}
+
+sealed trait MailboxReason {
+  def attr: MetaData
+}
+case class ResendReason(fromUserId: Long) extends MailboxReason {
+  def attr = new UnprefixedAttribute("resent_from", fromUserId.toString, Null)
+}
+case object NoReason extends MailboxReason {
+  def attr = Null
+}
+case class TrackReason(trackId: Long) extends MailboxReason {
+  def attr = new UnprefixedAttribute("track", trackId.toString, Null)
+}
+case class DirectReason(fromUserId: Long) extends MailboxReason {
+  def attr = new UnprefixedAttribute("direct", fromUserId.toString, Null)
+}
+case class ConversationReason(conversationId: Long) extends MailboxReason {
+  def attr = new UnprefixedAttribute("conversation", conversationId.toString, Null)
+}
+case class LoginReason(userId: Long) extends MailboxReason {
+  def attr = new UnprefixedAttribute("login", userId.toString, Null)
+}
+case class FollowedReason(userId: Long) extends MailboxReason {
+  def attr = new UnprefixedAttribute("followed", userId.toString, Null)
+}
+case class UnfollowedReason(userId: Long) extends MailboxReason {
+  def attr = new UnprefixedAttribute("unfollowed", userId.toString, Null)
+}
+case class ProfileReason(userId: Long) extends MailboxReason {
+  def attr = new UnprefixedAttribute("profile", userId.toString, Null)
+}
+case class RegularReason(actionId: Long) extends MailboxReason {
+  def attr = new UnprefixedAttribute("regular", actionId.toString, Null)
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Message.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Message.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Message.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Message.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,452 @@
+package org.apache.esme.model
+
+/**
+ * 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.
+ */
+
+import net.liftweb._
+import mapper._
+import util._
+import Helpers._
+import java.util.logging._
+
+import scala.xml._
+
+import org.compass.annotations._
+import bootstrap.liftweb.Compass.compass
+import org.compass.core._
+import lucene.util._
+import org.apache.lucene.index.TermFreqVector
+import net.sf.snowball.ext._
+
+import org.apache.esme._
+import lib._
+
+object Message extends Message with LongKeyedMetaMapper[Message] {
+  val logger: Logger = Logger.getLogger("org.apache.esme.model.Message")
+  logger.setLevel(Level.INFO)
+
+  private def fixConversation(msg: Message) {
+    if (!msg.conversation.defined_? && msg.replyTo.defined_?) {
+      for (replyTo <- msg.replyTo.obj) {
+        if (!replyTo.conversation.defined_?) {
+          replyTo.conversation(replyTo.id).save
+        }
+        msg.conversation(replyTo.conversation).save
+      }
+    }
+  }
+
+  def cacheSize: Int = 10000
+
+  private val idCache = new LRU[Long, Message](cacheSize)
+
+
+  def findMessages(in: Seq[Long]): Map[Long, Message] = synchronized {
+    val il = in.toList
+    val (r1, left) = il.foldLeft[(Map[Long, Message], List[Long])](
+      (Map.empty, Nil)) {
+      case ((map, left), id) => 
+        if (idCache.contains(id)) {
+          (map + (id -> idCache(id)), left)
+        } else (map, id :: left)
+    }
+    
+
+    val r2 = left match {
+      case Nil => r1
+      case xs =>
+        findAndPrime(InRaw(id, xs.mkString(","),
+                           IHaveValidatedThisSQL("dpp", "Aug 25, 2008"))).
+        foldLeft(r1) {
+          case (map, msg) =>
+            idCache(msg.id) = msg
+            map + (msg.id.is -> msg)
+        }
+    }
+
+    r2
+  }
+
+  private def uncache(msg: Message) = synchronized {
+    idCache.remove(msg.id)
+  }
+
+  
+  override def afterCommit = super.afterCommit
+
+  private def saveTags(msg: Message) {
+    msg.saveTheTags()
+  }
+
+  override def afterCreate = fixConversation _ ::
+  saveTags _ :: super.afterCreate
+
+  override def afterSave = uncache _ :: super.afterSave
+
+  def findAndPrime(params: QueryParam[Message]*): List[Message] = {
+    val ret: List[Message] = this.findAll(params :_*)
+
+
+    val userIds = (ret.flatMap(_.author.can) :::
+                   ret.flatMap(_.sentToIds)).removeDuplicates
+
+    val users = Map(User.findAll(InRaw(User.id, userIds.mkString(","),
+                                       IHaveValidatedThisSQL("dpp", "Aug 23, 2008"))).map(u => (u.id.is, u)) :_*)
+
+    ret.foreach(_.preload(users))
+    ret
+  }
+
+  def search(searchTerm: String, following: List[User], numHits: Int): List[Message] = {
+    val users:List[String] = following.map(user => user.nickname)
+
+    logger.info("Inside Message.search() with user list "+(users.mkString(", ")))
+
+    val session = compass.openSession()
+    var tx:CompassTransaction = null
+    var returnValue:List[Message] = Nil
+
+    try {
+      tx = session.beginTransaction()
+      val queryBuilder: CompassQueryBuilder = session.queryBuilder()
+
+      val followingQuery = queryBuilder.bool().addShould(queryBuilder.term("author", User.currentUser.open_!.id))
+      for (user <- following) followingQuery.addShould(queryBuilder.term("author", user.id))
+
+      val query: CompassQuery = queryBuilder.bool()
+      .addMust( queryBuilder.term("text", stemWord(searchTerm)) )
+      .addMust( followingQuery.toQuery )
+      .toQuery()
+
+      logger.info("query is "+query.toString)
+
+      val hitlist = query
+      .addSort("when", CompassQuery.SortPropertyType.STRING, CompassQuery.SortDirection.REVERSE)
+      .hits().detach(0, numHits)
+
+      logger.info("Detached hits: "+hitlist.totalLength)
+
+      val resourceList = hitlist.getResources.toList.asInstanceOf[List[Resource]]
+
+      returnValue = resourceList.map(x => Message.find(x.getId).open_!)
+      tx.commit();
+    } catch  {
+      case ce: CompassException =>
+        if (tx != null) tx.rollback();
+    } finally {
+      session.close();
+    }
+
+    returnValue
+  }
+}
+
+@Searchable
+class Message extends LongKeyedMapper[Message] {
+  def getSingleton = Message // what's the "meta" server
+  def primaryKeyField = id
+
+  object id extends MappedLongIndex(this)
+
+  object author extends MappedLongForeignKey(this, User)
+
+  object viaGroup extends MappedLongForeignKey(this, Group)
+
+  private[model] object text extends MappedText(this)
+
+  object when extends MappedLong(this) {
+    override def defaultValue = millis
+  }
+
+  object source extends MappedPoliteString(this, 32) {
+    def sourceAttr: Option[NodeSeq] = is match {
+      case null | "" => None
+      case xs => Some(Text(xs))
+    }
+  }
+
+  object replyTo extends MappedLongForeignKey(this, Message)
+
+  object conversation extends MappedLongForeignKey(this, Message)
+
+  private[model] def preload(users: Map[Long, User]) {
+    author.can.foreach(id => this.author.primeObj(users.get(id)))
+    primeNameMap(users)
+  }
+
+  private def replyToTag: MetaData =
+  new UnprefixedAttribute("reply_to",
+                          replyTo.can.map(i => Text(i.toString)).toOption,
+                          Null)
+
+  private def conversationTag: MetaData =
+  new UnprefixedAttribute("conversation",
+                          conversation.can.map(i => Text(i.toString)).toOption,
+                          Null)
+
+  override lazy val toXml: Elem = {
+    val org = originalXml
+
+    <message id={id.toString} source={source.sourceAttr} when={when.is.toString}
+      date={toInternetDate(when.is)}>
+      {
+        author.obj.map(u =>
+          <author name={u.niceName} id={u.id.toString}
+            image={u.image}/>
+        ) openOr Text("")
+      }
+      <body>{
+          (org \ "body").map(_.child map {
+              case e: Elem if e.label == "at_name" =>
+                e.attribute("id").map(id =>
+                  <at_name image={nameMap(id.text.toLong).image} id={id}
+                    nickname={nameMap(id.text.toLong).nickname.is}/>) getOrElse Text("")
+              case x => x
+            })
+        }</body>{
+        org \ "tags"
+      }{
+        org \ "metadata"
+      }</message> % replyToTag % conversationTag
+
+  }
+
+  lazy val digestedXHTML = {
+    (toXml \ "body").flatMap(_.child map {
+        case e: Elem if e.label == "at_name" =>
+          e.attribute("nickname").
+          map(nickname =>
+            <xml:group> @<a href={"/user/"+urlEncode(nickname.text)}>{nickname}</a> </xml:group>).
+          getOrElse(Text(""))
+
+        case e: Elem if e.label == "tag" =>
+          e.attribute("name").map(tag =>
+            <xml:group> #<a href={"/tag/"+urlEncode(tag.text)}>{tag}</a> </xml:group>).
+          getOrElse(Text(""))
+
+        case e: Elem if e.label == "url" =>
+          e.attribute("url").flatMap(url =>
+            e.attribute("uniqueId").map(id =>
+              <xml:group> <a href={"/u/"+id}>{url}</a> </xml:group>)).
+          getOrElse(Text("") )
+
+        case x => x
+      })
+  }
+
+  private lazy val originalXml = XML.loadString(text.is)
+
+  private [model] def saveTheTags() = synchronized {
+    for (tag <- tagIds) {
+      MessageTag.create.message(this).tag(tag).save
+    }
+    for (sentTo <- sentToIds)
+    MessageTag.create.message(this).sentTo(sentTo).save
+
+    for (urlId <- urlIds)
+    MessageTag.create.message(this).url(urlId).save
+  }
+
+  lazy val sentToIds: List[Long] =
+  (for (body <- originalXml \ "body";
+       at <- body \ "at_name";
+       id <- at.attribute("id")) yield id.text.toLong).toList
+
+  lazy val urlIds: List[Long] =
+  (for (body <- originalXml \ "body";
+       at <- body \ "url";
+       id <- at.attribute("id")) yield id.text.toLong).toList
+
+  private var _atNameMap: Map[Long, User] = Map.empty
+  private var _setNameMap = false
+
+  private def primeNameMap(in: Map[Long, User]) = synchronized {
+    _atNameMap = in
+    _setNameMap = true
+  }
+
+  lazy val nameMap: Map[Long, User] = synchronized {
+    if (_setNameMap) _atNameMap
+    else Map(User.findAll(InRaw(User.id, sentToIds.mkString(","),
+                                IHaveValidatedThisSQL("dpp", "August 23 2008"))).map(u => (u.id.is, u)) :_*)
+  }
+
+  lazy val tagIds: List[Long] =
+  (for (tag <- xmlTags;
+        id <- tag.attribute("id")) yield id.text.toLong).toList
+
+  lazy val tags: List[String] =
+  (for (tag <- xmlTags;
+        name <- tag.attribute("name")) yield name.text).toList
+
+  lazy val xmlTags: Seq[Node] =
+  for (tags <- (originalXml \ "tags");
+       tag <- tags \ "tag") yield tag
+
+  // Define getter methods for Compass to use
+  @SearchableId
+  def getId:Long = id
+
+  @SearchableProperty
+  def getAuthor:Long = author.is
+
+  // termVector=YES means that we get the word frequencies for tag clouds
+  @SearchableProperty{val termVector=TermVector.YES, val analyzer="stemming"}
+  def getText:String = originalXml.text
+
+  @SearchableProperty{val termVector=TermVector.YES, val analyzer="default"}
+  def getTextWords:String = originalXml.text
+
+  @SearchableProperty{val format="yyyy-MM-dd mm:ss"}
+  def getWhen = new java.util.Date(when.is)
+
+  @SearchableProperty{val termVector=TermVector.YES, val analyzer="tag"}
+  def getTags:String = {
+    // Create a string of space-separated tags, with the spaces in each tag converted to underscores
+    tags.map(x => x.split(" ").mkString("_")) mkString " "
+  }
+
+  def setTextAndTags(in: String, tags: List[Tag], metaData: Box[Elem]): Box[Message] = {
+    MsgParser.parseMessage(in).map{
+      lst =>
+      val xml = <message><body>{
+            lst map {
+              case HashTag(t) => t.toXml
+              case AtName(user) => <at_name id={user.id.toString}
+                  nickname={user.nickname.is} />
+              case MsgText(text) => Text(text)
+              case URL(url) => <url id={url.id.toString}
+                  url={url.url.toString} uniqueId={url.uniqueId.is} />
+            }
+          }</body>
+        <tags>{
+            ((lst.flatMap{case HashTag(t) => Full(t) case _ => Empty})
+             ::: tags).removeDuplicates.map(_.toXml)
+          }</tags>{
+          metaData match {
+            case Full(xs) => <metadata>xs</metadata>
+            case _ => NodeSeq.Empty
+          }
+
+        }</message>
+      this.text(xml.toString)
+      this
+    }
+  }
+
+  // Because Message.getClass returns a ref to Message$
+  def clazz = this.getClass()
+
+  // Get the term (i.e. word) frequencies for the word cloud from the message text
+  lazy val wordFrequencies: List[(String, Int)] = {
+    val session = compass.openSession()
+    var tx:CompassTransaction = null
+    var returnValue:List[(String, Int)] = Nil
+
+    try {
+      tx = session.beginTransaction()
+
+      val msgResource = session.getResource(clazz, id) match {
+        case null =>  Message.logger.info("Saving entity to lucene in wordFrequencies")
+          session.save(this)
+          session.loadResource(clazz, id)  // throws exception if not found
+
+        case x => x
+      }
+      
+      val textTermFreqs:TermFreqVector = LuceneHelper.getTermFreqVector(session, msgResource, "textWords")
+      Message.logger.info("textTermFreqs: "+textTermFreqs)
+
+      def termsAndFreq(in: TermFreqVector) = in match {
+        case null => Nil
+        case tf => (tf.getTerms zip tf.getTermFrequencies).toList
+      }
+
+      returnValue = termsAndFreq(textTermFreqs)
+
+      tx.commit();
+    } catch  {
+      case ce: CompassException =>
+        if (tx != null) tx.rollback();
+    } finally {
+      session.close();
+    }
+
+    compoundStem(returnValue)
+  }
+
+  // Get the tag frequencies for the tag cloud from the message's tags
+  lazy val tagFrequencies: List[(String, Int)] = {
+    tags.map((_, 1)).toList
+  }
+
+  def centreWeightedTopNWordFreqs(messages: List[Message], n:Int):List[(String, Float)] = {
+    val weights = compoundStem(messages.flatMap(_.wordFrequencies))
+
+    // Start with the top N tags
+    val sortedWeights = weights.sort(_._2 > _._2).take(n)
+
+    // And create a normalized cente-weighted list, e.g. smallest, small, Larger, BIG, *HUGE*, BIG, Larger, small, smallest
+    TagUtils.normalize(TagUtils.everyEven(sortedWeights).reverse ::: TagUtils.everyOdd(sortedWeights))
+  }
+
+  /**
+   * Stem an incoming string
+   */
+  private val stemmer:PorterStemmer = new PorterStemmer()
+  def stemWord(in: String): String = stemmer.synchronized {
+    stemmer.setCurrent(in)
+    stemmer.stem()
+    stemmer.getCurrent()
+  }
+
+  // Compounds a bunch of (String, Int) elements so that [(String1, Int1), (String2, Int2)] becomes [(StringN, Int1+Int2)]
+  //  if String1 and String2 have the same stem (according to the Porter stemming algorithm). StringN is the shorter of
+  //  String1 and String2
+  private[model] def compoundStem(llsi: List[(String,Int)]): List[(String,Int)] = {
+    val stemCache = llsi.foldLeft[Map[String, String]](Map.empty){
+      case (map, (str, _)) => if (map.contains(str)) map
+        else map + (str -> stemWord(str))
+    }
+    def shortWord(a: String, b: String): String =
+    if (a.length < b.length) a else b
+
+    val stemToWord: Map[String, String] = Map(
+      // create a map from stem to all the words that
+      // stem down to that word
+      stemCache.toList.
+      foldLeft[Map[String, List[String]]](Map.empty){
+        case (map, (word, stem)) =>
+          map + (stem -> (word :: map.getOrElse(stem, Nil)))
+      }.toList.
+      // convert the list of stemmed words to the shortest word
+      // matching the stem
+      map{
+        case (key, value) => (key, value.reduceLeft(shortWord))
+      } :_*)
+
+    llsi.foldLeft[Map[String, Int]](Map.empty){
+      case (map, (str, cnt)) =>
+        val sw = stemCache(str)
+        map + (sw -> (map.getOrElse(sw, 0) + cnt))
+    }.toList.map{ case (stem, cnt) => (stemToWord(stem), cnt)}
+  }
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/MessageTag.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/MessageTag.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/MessageTag.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/MessageTag.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,43 @@
+package org.apache.esme.model
+
+/**
+ * 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.
+ */
+
+import net.liftweb._
+import mapper._
+import util._
+import Helpers._
+
+object MessageTag extends MessageTag with LongKeyedMetaMapper[MessageTag] {
+
+}
+
+class MessageTag extends LongKeyedMapper[MessageTag] {
+  def getSingleton = MessageTag // what's the "meta" server
+  def primaryKeyField = id
+
+  object id extends MappedLongIndex(this)
+  
+  object message extends MappedLongForeignKey(this, Message)
+  object tag extends MappedLongForeignKey(this, Tag) // MappedText(this)
+  object sentTo extends MappedLongForeignKey(this, User)
+  object url extends MappedLongForeignKey(this, UrlStore)
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Relationship.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Relationship.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Relationship.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Relationship.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,39 @@
+package org.apache.esme.model
+
+/**
+ * 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.
+ */
+
+import net.liftweb._
+import mapper._
+import util._
+
+object Relationship extends Relationship with LongKeyedMetaMapper[Relationship] {
+
+}
+
+class Relationship extends LongKeyedMapper[Relationship] {
+  def getSingleton = Relationship // what's the "meta" server
+  def primaryKeyField = id
+
+  object id extends MappedLongIndex(this)
+  object owner extends MappedLongForeignKey(this, User)
+  object target extends MappedLongForeignKey(this, User)
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Tag.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Tag.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Tag.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Tag.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,77 @@
+package org.apache.esme.model
+
+/**
+ * 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.
+ */
+
+import net.liftweb._
+import http._
+import mapper._
+import util._
+import Helpers._
+
+import scala.collection.mutable.HashMap
+import java.util.logging._
+
+import scala.actors._
+import Actor._
+
+import org.apache.esme.lib.TagUtils
+
+object Tag extends Tag with MetaProtoTag[Tag] {
+  override def dbTableName = "tag" // define the DB table name
+
+  def cacheSize = 500
+  
+  val logger: Logger = Logger.getLogger("org.apache.esme.model")
+  logger.setLevel(Level.WARNING);
+  
+  private var listeners: List[Actor] = Nil
+  private var cloud: List[(String, Float)] = Nil
+
+  // Compounds a bunch of (String, Int) elements so that [(String1, Int1), (String1, Int2)] becomes [(String1, Int1+Int2)]
+  private[model] def compound(llsi: List[(String,Int)]): List[(String,Int)] = {
+    llsi.foldLeft[Map[String, Int]](Map.empty){
+        case (map, (str, cnt)) =>
+          map + (str -> (map.getOrElse(str, 0) + cnt))
+      }.toList
+  }
+
+  def centreWeightedTopNTagFreqs(messages: List[Message], n:Int):List[(String, Float)] = {
+    val weights = compound(messages.flatMap(_.tagFrequencies))
+
+    // Start with the top 20 tags, sorted by frequency
+    val sortedWeights = weights.sort(_._2 > _._2).take(n)
+
+    // And create a normalized cente-weighted list, e.g. smallest, small, Larger, BIG, *HUGE*, BIG, Larger, small, smallest
+    TagUtils.normalize(TagUtils.everyEven(sortedWeights).reverse ::: TagUtils.everyOdd(sortedWeights))
+  }
+  
+}
+
+class Tag extends ProtoTag[Tag] {
+  def getSingleton = Tag // what's the "meta" server
+  
+  def findMessages(): List[Message] =
+  Message.findAndPrime(In.fk(MessageTag.message, By(MessageTag.tag, this)),
+		       OrderBy(Message.id, Descending))
+
+  override def toXml = <tag id={id.is.toString} name={name.is}/>
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Tracking.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Tracking.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Tracking.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Tracking.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,142 @@
+package org.apache.esme.model
+
+/**
+ * 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.
+ */
+
+import net.liftweb._
+import mapper._
+import util._
+import Helpers._
+import http._
+
+import org.apache.esme._
+import actor.Distributor
+
+import scala.xml.{Node, Text, Elem}
+
+object Tracking extends Tracking with LongKeyedMetaMapper[Tracking] {
+  override def afterCommit = notifyDistributor _ :: super.afterCommit
+
+  private def notifyDistributor(in: Tracking) {
+    Distributor ! Distributor.UpdateTrackingFor(in.user, 
+    Distributor.TrackTrackingType)
+  }
+}
+
+class Tracking extends LongKeyedMapper[Tracking] {
+  def getSingleton = Tracking // what's the "meta" server
+  def primaryKeyField = id
+
+  object id extends MappedLongIndex(this)
+
+  override def toXml: Elem = 
+  <tracking id={id.toString} 
+    user={user.can.map(l => Text(l.toString)).toOption}
+    pattern={pattern}
+    removed={removed.toString}
+    createdAt={createdAt.toString}></tracking>
+
+    object user extends MappedLongForeignKey(this, User)
+    // object regex extends MappedString(this, 256)
+    object removed extends MappedBoolean(this) {
+      override def defaultValue = false
+    }
+    object createdAt extends MappedLong(this) {
+      override def defaultValue = millis
+    }
+    object disabled extends MappedBoolean(this) {
+      override def defaultValue = false
+    }
+    private[model] object what extends MappedString(this, 512)
+
+object action extends MappedString(this, 2048)
+  
+    object who extends MappedLongForeignKey(this, User)
+
+object uniqueId extends MappedUniqueId(this, 24) {
+  override def dbIndexed_? = true
+}
+
+    def pattern: String = who.obj match {
+      case Full(who) => "@"+who.niceName
+      case _ => what.is
+    }
+
+    def regex(in: String): Tracking = {
+      val i2 = in.trim
+      if (i2.startsWith("@")) {
+        who(User.find(By(User.nickname, i2.substring(1).trim)))
+      }
+      what(i2)
+    }
+
+    def matcher: Box[TrackingMatcher] = {
+      who.can match {
+        case Full(whoId) =>
+          Full(new PersonTrackingMatcher(id, whoId))
+
+        case _ =>
+          if (what.startsWith("#"))
+          Full(new TagTrackingMatcher(id, Tag.capify(what.substring(1))))
+          else
+          tryo(new RegexTrackingMatcher(id, what.is))
+      }
+    }
+    }
+
+    sealed trait TrackingMatcher extends Ordered[TrackingMatcher] {
+      def doesMatch_?(in: Message) : Boolean
+      def trackId: Long
+      // def onReceipt: Boolean
+    }
+
+    case class PersonTrackingMatcher(trackId: Long, whoId: Long) extends TrackingMatcher {
+      def doesMatch_?(in: Message): Boolean = in.author.is == whoId
+      
+      def compare(other: TrackingMatcher): Int = other match {
+        case PersonTrackingMatcher(_, otherWho) => whoId.compare(otherWho)
+        case _ => -1
+      }
+    }
+
+    case class TagTrackingMatcher(trackId: Long, tag: String)
+    extends TrackingMatcher {
+      def doesMatch_?(in: Message): Boolean = in.tags.exists(_ == tag)
+      def compare(other: TrackingMatcher): Int = other match {
+        case PersonTrackingMatcher(_, _) => 1
+        case TagTrackingMatcher(_, otherTag) => tag.compare(otherTag)
+        case _ => -1
+      }
+    }
+
+    case class RegexTrackingMatcher(trackId: Long, re: String) extends TrackingMatcher {
+      private val regex = re.r
+  
+      def doesMatch_?(in: Message): Boolean =
+      regex.findFirstIn(in.getText).isDefined
+      
+      def compare(other: TrackingMatcher): Int = other match {
+        case RegexTrackingMatcher(_, otherRe) => re.compare(otherRe)
+        case _ => 1
+      }
+    }
+
+

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/UrlStore.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/UrlStore.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/UrlStore.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/UrlStore.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,70 @@
+package org.apache.esme.model
+
+/**
+ * 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.
+ */
+
+import net.liftweb._
+import mapper._
+import util._
+import http._
+
+object UrlStore extends UrlStore with LongKeyedMetaMapper[UrlStore] {
+  def redirectizer: LiftRules.DispatchPF = {
+    case Req("u" :: id :: Nil, "", GetRequest) =>
+      serve(id)
+  }
+
+  private def serve(id: String)(): Box[LiftResponse] = 
+  for (url <- find(By(uniqueId, id)))
+  yield RedirectResponse(url.url)
+
+  def make(in: String): UrlStore = {
+    find(By(url, in)) match {
+      case Full(r) => r
+      case _ => UrlStore.create.url(in).saveMe
+    }
+  }
+  
+  def urlFrom(in: String): String =
+  find(By(uniqueId, in)).map(_.url.is) openOr
+  "http://google.com"
+}
+
+class UrlStore extends LongKeyedMapper[UrlStore] {
+  def getSingleton = UrlStore // what's the "meta" server
+  def primaryKeyField = id
+
+  object id extends MappedLongIndex(this)
+
+  object url extends MappedPoliteString(this, 512) {
+    override def validations =
+    this.valMinLen(3, "The minimum group length is 3 characters") _ ::
+    super.validations
+
+    override def setFilter = trim _ :: super.setFilter
+
+    override def dbIndexed_? = true
+  }
+
+  object uniqueId extends MappedUniqueId(this, 16) {
+    override def dbIndexed_? = true
+  }
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/User.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/User.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/User.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/User.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,311 @@
+package org.apache.esme.model
+/**
+ * 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.
+ */
+
+import net.liftweb._
+import mapper._
+import sitemap._
+import util._
+import openid._
+import http._
+import js._
+import JsCmds._
+import Helpers._
+import sitemap._
+import Loc._
+
+import org.openid4java.discovery.Identifier
+import org.openid4java.consumer._
+import org.openid4java.util._
+
+import scala.xml.{NodeSeq, Text}
+
+import org.apache.esme._
+import actor._
+import view._
+import java.net.URL
+import java.util.logging._
+
+object User extends User with MetaOpenIDProtoUser[User] {
+  val logger: Logger = Logger.getLogger("org.apache.esme.model.User")
+  logger.setLevel(Level.INFO)
+
+  override def afterSave = profileChanged _ :: notifyActors _ :: super.afterSave
+
+  private def notifyActors(in: User) {
+    Distributor ! Distributor.UserUpdated(in.id)
+  }
+
+  private def profileChanged(in: User) {
+    if (!in.needsChange_?)
+      Message.create.author(in.id).
+                     when(Helpers.timeNow.getTime).
+                     source("profile").
+                     setTextAndTags("User " + in.nickname + " changed profile. Name: " + in.wholeName + ", Image: " + in.imageUrl, Nil, Empty).
+                     foreach{ msg => 
+                       if (msg.save) {
+                         Distributor ! Distributor.AddMessageToMailbox(in.id, msg, ProfileReason(in.id)) 
+                       }
+                     }
+  }
+
+  def findFromWeb(uid: String): Box[User] = 
+  User.find(By(User.nickname, uid)) or User.find(uid)
+
+  override def dbTableName = "users" // define the DB table name
+  
+  override def screenWrap = S.request.flatMap(_.location) match {
+    case Full(l) if l.name == "Login" => Full(<lift:surround with="login" at="content">
+          <lift:bind /></lift:surround>)
+    case _ => Full(<lift:surround with="default" at="content">
+          <lift:bind /></lift:surround>)
+  }
+
+  /**
+   * The menu item for editing the user (make this "Empty" to disable)
+   */
+  override def editUserMenuLoc: Box[Menu] =
+  Full(Menu(Loc("EditUser", editPath, "Profile",
+                Template(() => wrapIt(editFunc.map(_()) openOr edit)),
+                testLogginIn)))
+
+  
+  
+  override def signupFields: List[BaseOwnedMappedField[User]] = nickname ::
+  firstName :: lastName :: imageUrl :: timezone :: locale :: Nil
+  
+  override def fieldOrder: List[BaseOwnedMappedField[User]] = nickname ::
+  firstName :: lastName :: imageUrl :: timezone :: locale :: Nil
+
+  onLogIn = ExtSession.userDidLogin _ :: onLogIn
+
+  onLogOut =  ExtSession.userDidLogout _ :: onLogOut
+  
+  override def loginXhtml =
+  <form id="openid_submit" class="clear" method="POST" action={loginPath.mkString("/", "/", "")} >
+    <div class="b-open-l">
+      <p class="input"><label>Open ID</label><user:openid /></p>
+      <p class="button">
+        <img onclick="document.getElementById('openid_submit').submit()" src="/images/sign-on.png" alt="Sign On" />
+      </p>
+    </div>
+    {
+      if (openIdError.is)
+      <div class="b-open-r">
+        <h3>Oops!</h3>
+        <p>We are not able validate your ID. Please try again.</p>
+      </div>
+      else Text("")
+    }
+  </form>
+
+  object openIdError extends RequestVar(false)
+  
+  override def login = {
+    if (S.post_?) {
+      S.param("username").
+      foreach(username => 
+        ESMEOpenIDVendor.loginAndRedirect(username, logUserIn)
+      )
+    }
+    
+    def logUserIn(openid: Box[Identifier], fo: Box[VerificationResult], exp: Box[Exception]): LiftResponse = {
+      (openid, exp) match {
+        case (Full(id), _) =>
+          val user = User.findOrCreate(id.getIdentifier)
+          User.logUserIn(user)
+          S.notice("Welcome "+user.niceName)
+
+          Message.create.author(user.id).
+                         when(Helpers.timeNow.getTime).
+                         source("login").
+                         setTextAndTags("User " + user.nickname + " logged in.", Nil, Empty).
+                         foreach{ msg => 
+                           if (msg.save) {
+                             Distributor ! Distributor.AddMessageToMailbox(user.id, msg, LoginReason(user.id)) 
+                           }
+                         }
+
+          RedirectResponse("/", S responseCookies :_*)
+          
+        case (_, Full(exp)) =>
+          openIdError(true)
+          S.error("Got an exception: "+exp.getMessage)
+          RedirectResponse(S.uri, S responseCookies :_*)
+
+        case _ =>
+          openIdError(true)
+          S.error("Unable to log you in: "+fo.map(_.getStatusMsg))
+          RedirectResponse(S.uri, S responseCookies :_*)
+      }
+
+      
+    }
+    
+    loginForm
+  }
+  
+  def loginForm =     bind("user", loginXhtml,
+                           "openid" -> (FocusOnLoad(<input type="text" name="username"/>)))
+  
+  def openIDVendor = ESMEOpenIDVendor
+  
+  override def logout = {
+    logoutCurrentUser
+    S.redirectTo("/static/about")
+  }
+
+  def followerIdsForUserId(userId: Long): List[Long] =
+  Relationship.findAll(By(Relationship.target, userId)).map(_.owner.is)
+}
+
+object ESMEOpenIDVendor extends OpenIdVendor {
+  type UserType = User
+  type ConsumerType = ESMEOpenIDConsumer
+
+  def logUserOut(): Unit = User.logUserOut()
+  
+  def currentUser = User.currentUser
+  
+  def postLogin(id: Box[Identifier],res: VerificationResult): Unit = {
+    id match {
+      case Full(id) =>
+        val user = User.findOrCreate(id.getIdentifier())
+        User.logUserIn(user)
+        S.notice("Welcome "+user.niceName)
+
+      case _ =>
+        logUserOut()
+        S.error("Failed to authenticate")
+    }
+  }
+  
+  def displayUser(in: User): NodeSeq = Text("Welcome "+in.niceName)
+  
+  def createAConsumer = new ESMEOpenIDConsumer
+}
+
+class ESMEOpenIDConsumer extends OpenIDConsumer[User]
+{
+  override val manager = {
+
+    User.logger.info("Proxy settings: " + Props.get("http.proxyHost", "[no host]")
+                       + ":" + Props.get("http.proxyPort", "[no port]"))
+
+    for (host <- Props.get("http.proxyHost")){
+      val proxyProps = new ProxyProperties()
+      proxyProps.setProxyHostName(host)
+      proxyProps.setProxyPort(Props.getInt("http.proxyPort", 80))
+      HttpClientFactory.setProxyProperties(proxyProps)
+    }
+    new ConsumerManager
+  }
+}
+
+/**
+ * An O-R mapped "User" class that includes first name, last name, password
+ */
+class User extends OpenIDProtoUser[User] {
+  def getSingleton = User // what's the "meta" server
+
+  object imageUrl extends MappedString(this, 256)
+  
+  def authTokens: List[AuthToken] =
+  AuthToken.findAll(By(AuthToken. user, this),
+                    OrderBy(AuthToken.description, Ascending))
+
+
+  override lazy val toXml =
+  <user id={id.toString} nickname={niceName} image={image} whole_name={wholeName} />
+
+  def follow(who: User): Boolean = {
+    if (who == this) false
+    else
+    Relationship.find(By(Relationship.owner, this),
+                      By(Relationship.target, who)) match {
+      case Full(x) => true
+      case Empty => { if (Relationship.create.owner(this).target(who).save)
+        Message.create.author(who.id).
+                       when(Helpers.timeNow.getTime).
+                       source("followed").
+                       setTextAndTags("User " + this.nickname + " followed " + who.nickname + ".", Nil, Empty).
+                       foreach { msg =>
+                         if (msg.save) {
+                           Distributor ! Distributor.AddMessageToMailbox(who.id, msg, FollowedReason(this.id))
+                         }
+                       }
+        true
+      }
+      case _ => false
+    }
+  }
+
+  def unfollow(who: User): Boolean = {
+    Relationship.findAll(By(Relationship.owner, this),
+                         By(Relationship.target, who)).foreach{ r =>
+                           if (r.delete_!) Message.create.author(who.id).
+                                           when(Helpers.timeNow.getTime).
+                                           source("unfollowed").
+                                           setTextAndTags("User " + this.nickname + " unfollowed " + who.nickname + ".", Nil, Empty).
+                                           foreach{ msg =>
+                                             if (msg.save) {
+                                               Distributor ! Distributor.AddMessageToMailbox(who.id, msg, UnfollowedReason(this.id))
+                                             }
+                                           }
+                         }
+    true
+  }
+
+  def following_?(who: User): Boolean = 
+  Relationship.find(By(Relationship.owner, this),
+                    By(Relationship.target, who)).isDefined
+
+  def following(): List[User] =
+  User.findAll(In.fk(Relationship.target, By(Relationship.owner, this)))
+
+  def followers(): List[User] =
+  User.findAll(In.fk(Relationship.owner, By(Relationship.target, this)))
+  
+  def wholeName: String = (firstName.is, lastName.is) match {
+    case (f, l) if f.length > 1 && l.length > 1 => f+" "+l
+    case (f, _) if f.length > 1 => f
+    case (_, l) if l.length > 1 => l
+    case (_, _) => niceName
+  }
+
+  def needsChange_? : Boolean = this.nickname.is.startsWith("chang") &&
+  this.firstName.startsWith("Unkn") && this.lastName.startsWith("Unkn")
+
+  def image: Option[NodeSeq] = tryo(Text((new URL(imageUrl)).toString)).toOption
+
+  def tracking: List[Tracking] = 
+  Tracking.findAll(By(Tracking.user, this),
+                   By(Tracking.disabled, false),
+                   By(Tracking.removed, false),
+                   OrderBy(Tracking.id, Ascending))
+  
+  def performing: List[Action] =
+  Action.findAll(By(Action.user, this),
+                 By(Action.disabled, false),
+                 By(Action.removed, false),
+                 OrderBy(Action.id, Ascending))
+
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/snippet/.keep
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/snippet/.keep?rev=736864&view=auto
==============================================================================
    (empty)

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/snippet/Style.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/snippet/Style.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/snippet/Style.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/snippet/Style.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,52 @@
+/**
+ * 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.
+ */
+
+package org.apache.esme.snippet
+
+import net.liftweb._
+import http._
+import util._
+import js._
+import JE._
+import JsCmds._
+import Helpers._
+
+import org.apache.esme._
+import model._
+import actor._
+
+import scala.xml.{NodeSeq, Unparsed}
+
+
+class Style {
+  def header: NodeSeq = {
+    Unparsed(
+"""
+    <!--[if gt IE 7]><!--><link rel="stylesheet" href='"""+
+    S.contextPath+
+    """/style/esme.css'/><!--<![endif]-->
+    <!--[if lt IE 8]><link rel=stylesheet href='"""+
+    S.contextPath+"""/style/esme-ie.css'><![endif]-->
+
+"""
+    )
+  }
+}