esme-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From d..@apache.org
Subject svn commit: r726084 [2/6] - in /incubator/esme/trunk/server: ./ src/ src/main/ src/main/resources/ src/main/resources/props/ src/main/scala/ src/main/scala/bootstrap/ src/main/scala/bootstrap/liftweb/ src/main/scala/us/ src/main/scala/us/esme/ src/main...
Date Fri, 12 Dec 2008 18:32:22 GMT
Added: incubator/esme/trunk/server/src/main/scala/us/esme/lib/MsgParser.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/lib/MsgParser.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/lib/MsgParser.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/lib/MsgParser.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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 us.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 => CanFailure}
+import scala.util.parsing.input.Reader
+import scala.xml.{XML, NodeSeq, Node, Text}
+import us.esme._
+import model._
+
+object MsgParser extends Parsers with ImplicitConversions with CombParserHelpers {
+  def parseMessage(in: String): Can[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] = 
+  opt(user ~ opt( ':' ~>  password ) <~ '@' ) ~ hostport ^^ {
+    case None ~ hostport => hostport
+    case Some(user ~ pwd) ~ hostport => user+(pwd.map(p => ":"+p))+"@"+hostport
+  }
+
+  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 httpUrl: Parser[String] = (accept("http://") | accept("https://")) ~ hostport ~ 
+  opt( '/' ~> hpath ~ opt('?' ~> search )) ^^ {
+    case front ~ hp ~ None => front.mkString + hp
+    case front ~ hp ~ Some(pth ~ None) => front.mkString + hp + "/" + pth
+    case front ~ hp ~ Some(pth ~ Some(search)) =>
+      front.mkString + 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): Can[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 <~ EOF ^^ {case mt => MailTo(mt)}) |
+  (httpUrl ~ rep(httpHeader) <~ EOF ^^ {
+      case http ~ hdrs => HttpTo(http, hdrs)
+    })
+
+  lazy val httpHeader: Parser[(String, String)] = EOL ~ accept("header:") ~
+  lineSpace ~> rep1(uchar) ~ '=' ~ rep1(uchar) ^^ {
+    case name ~ _ ~ value => (name.mkString, value.mkString)
+  }
+
+  def testMessage(in: String): Can[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 |
+  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 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): Can[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/us/esme/lib/TagUtils.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/lib/TagUtils.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/lib/TagUtils.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/lib/TagUtils.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,38 @@
+/*
+ * TagUtils.scala
+ *
+ * To change this template, choose Tools | Template Manager
+ * and open the template in the editor.
+ */
+
+package us.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/us/esme/model/.keep
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/.keep?rev=726084&view=auto
==============================================================================
    (empty)

Added: incubator/esme/trunk/server/src/main/scala/us/esme/model/Action.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/Action.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/model/Action.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/model/Action.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,342 @@
+package us.esme.model
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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 us.esme._
+import lib._
+import actor._
+
+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)
+  }
+  
+  type TestFunc = (Message, Long, Calendar) => Boolean
+  
+  lazy val TrueFunc: TestFunc = {case _ => true}
+  lazy val SentToMe: TestFunc = (m, u, c) => 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) => !f(m,u,c)
+
+    case OrAction(left, right) =>
+      val f1 = toFunc(left)
+      val f2 = toFunc(right)
+      (m, u, c) => f1(m, u, c) || f2(m, u, c)
+  
+    case AndAction(left, right) =>
+      val f1 = toFunc(left)
+      val f2 = toFunc(right)
+      (m, u, c) => f1(m, u, c) && f2(m, u, c)
+  
+    case AtUserAction(userId) =>
+      (m, u, c) => m.author.is == userId 
+        
+    case SentToMeAction =>
+      SentToMe
+      
+    case RegexAction(re) =>
+      val r = re.r
+      (m, u, c) => r.findFirstIn(m.getText).isDefined
+        
+    case StringAction(s) =>
+      val str = s.toLowerCase.trim
+      (m, u, c) => m.getText.toLowerCase.indexOf(str) >= 0
+        
+    case HashAction(id, _) =>
+      (m, u, c) => m.tagIds.contains(id)
+        
+    case ParenAction(a) =>
+      toFunc(a)
+        
+    case PercentAction(percent) =>
+      (m, u, c) => Helpers.randomInt(100) <= percent
+      
+    case  AtSendAction(users, EqOpr) =>
+      (m, u, c) => !m.sentToIds.intersect(users).isEmpty
+
+    case  AtSendAction(users, NeOpr) =>
+      (m, u, c) => m.sentToIds.intersect(users).isEmpty
+      
+    case DateTestAction(dt, ot, what) =>
+      (m, u, c) => ot.buildFunc(dt.buildFunc(c), what)
+  }
+}
+
+class Action extends LongKeyedMapper[Action] {
+  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): Can[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 actionText = theAction.is
+  
+  def setAction(in: String): Can[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): Boolean =
+  func(msg, userId, cal)
+
+  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 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) extends Performances
+case class HttpTo(url: String, headers: List[(String, String)]) extends Performances
+case object PerformResend extends Performances
+case object PerformFilter extends Performances

Added: incubator/esme/trunk/server/src/main/scala/us/esme/model/AuthToken.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/AuthToken.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/model/AuthToken.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/model/AuthToken.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,58 @@
+package us.esme.model
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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)
+}
+*/
\ No newline at end of file

Added: incubator/esme/trunk/server/src/main/scala/us/esme/model/DidPerform.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/DidPerform.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/model/DidPerform.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/model/DidPerform.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,40 @@
+package us.esme.model
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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/us/esme/model/ExtSession.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/ExtSession.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/model/ExtSession.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/model/ExtSession.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,36 @@
+package us.esme.model
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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: Can[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/us/esme/model/Group.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/Group.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/model/Group.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/model/Group.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,45 @@
+package us.esme.model
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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): Can[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/us/esme/model/Mailbox.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/Mailbox.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/model/Mailbox.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/model/Mailbox.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,78 @@
+package us.esme.model
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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)
+
+  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)
+}

Added: incubator/esme/trunk/server/src/main/scala/us/esme/model/Message.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/Message.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/model/Message.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/model/Message.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,448 @@
+package us.esme.model
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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 us.esme._
+import lib._
+
+object Message extends Message with LongKeyedMetaMapper[Message] {
+  val logger: Logger = Logger.getLogger("us.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: Can[Elem]): Can[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/us/esme/model/MessageTag.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/MessageTag.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/model/MessageTag.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/model/MessageTag.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,39 @@
+package us.esme.model
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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/us/esme/model/Relationship.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/Relationship.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/model/Relationship.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/model/Relationship.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,35 @@
+package us.esme.model
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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/us/esme/model/Tag.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/Tag.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/model/Tag.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/model/Tag.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,73 @@
+package us.esme.model
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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 us.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("us.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/us/esme/model/Tracking.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/Tracking.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/model/Tracking.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/model/Tracking.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,138 @@
+package us.esme.model
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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 us.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: Can[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/us/esme/model/UrlStore.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/UrlStore.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/model/UrlStore.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/model/UrlStore.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,66 @@
+package us.esme.model
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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)(): Can[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/us/esme/model/User.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/model/User.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/model/User.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/model/User.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,261 @@
+package us.esme.model
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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/licenseUsers/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 us.esme._
+import actor._
+import java.net.URL
+import java.util.logging._
+
+object User extends User with MetaOpenIDProtoUser[User] {
+  val logger: Logger = Logger.getLogger("us.esme.model.User")
+  logger.setLevel(Level.INFO)
+
+  override def afterSave = notifyActors _ :: super.afterSave
+
+  private def notifyActors(in: User) {
+    Distributor ! Distributor.UserUpdated(in.id)
+  }
+
+
+  def findFromWeb(uid: String): Can[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: Can[Menu] =
+  Full(Menu(Loc("EditUser", editPath, "Profile", 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" />
+        <span class="note">...if you dare...</span>
+      </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: Can[Identifier], fo: Can[VerificationResult], exp: Can[Exception]): LiftResponse = {
+      (openid, exp) match {
+        case (Full(id), _) =>
+          val user = User.findOrCreate(id.getIdentifier)
+          User.logUserIn(user)
+          S.notice("Welcome "+user.niceName)
+          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: Can[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 => Relationship.create.owner(this).
+        target(who).save
+      case _ => false
+    }
+  }
+
+  def unfollow(who: User): Boolean = {
+    Relationship.findAll(By(Relationship.owner, this),
+                         By(Relationship.target, who)).foreach(_.delete_!)
+    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/us/esme/snippet/.keep
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/snippet/.keep?rev=726084&view=auto
==============================================================================
    (empty)

Added: incubator/esme/trunk/server/src/main/scala/us/esme/snippet/Style.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/snippet/Style.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/snippet/Style.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/snippet/Style.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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.
+ */
+
+/*
+ * MainScreen.scala.scala
+ *
+ * To change this template, choose Tools | Template Manager
+ * and open the template in the editor.
+ */
+
+package us.esme.snippet
+
+import net.liftweb._
+import http._
+import util._
+import js._
+import JE._
+import JsCmds._
+import Helpers._
+
+import us.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]-->
+
+"""
+    )
+  }
+}

Added: incubator/esme/trunk/server/src/main/scala/us/esme/snippet/UserSnip.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/snippet/UserSnip.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/snippet/UserSnip.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/snippet/UserSnip.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,125 @@
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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 us.esme.snippet
+
+import us.esme._
+import model._
+import actor._
+
+import net.liftweb._
+import http._
+import js._
+import JsCmds._
+import JE._
+import util._
+import Helpers._
+
+import scala.xml.{NodeSeq, Text, Node}
+
+object JsonPoster extends SessionVar(S.buildJsonFunc{
+    case JsonCmd("post", _, map: Map[String, Any], _) =>
+      for (msgObj <- map.get("msg");
+           msg <- Can.isA(msgObj, classOf[String]).map(_.trim) if msg.length > 0;
+           tagObj <- map.get("tags");
+           tags <- Can.isA(tagObj, classOf[String]);
+           user <- User.currentUser) {
+        
+        val replyTo = map.get("reply-to").map(toLong) match {
+          case Some(x) if x > 0L => Full(x)
+          case _ => Empty
+        }
+        
+        Distributor ! 
+        Distributor.UserCreatedMessage(user.id, msg, 
+                                       Tag.split(tags),
+                                       millis, 
+                                       Empty,
+                                       "web",
+                                       replyTo)
+                                       
+      }
+      Noop
+
+    case _ => Noop
+  }
+)
+
+class UserSnip extends DispatchSnippet {
+  def dispatch: DispatchIt = 
+  Map("name" -> userName _,
+      "postScript" -> postScript _,
+      "followers" -> followers _,
+      "following" -> following _,
+      "loginForm" -> loginForm _,
+      "loggedIn" -> loggedInFilter _)
+
+  def loggedInFilter(in: NodeSeq): NodeSeq = {
+    val lookFor = if (User.loggedIn_?) "in" else "out"
+    
+    (<foo>{in}</foo> \ lookFor).toList.
+    filter(_.prefix == "logged").
+    map(_.child).firstOption.getOrElse(NodeSeq.Empty)
+  }
+
+  def userFmt(u: User): Node = 
+  <li><a href={"/user/"+urlEncode(u.nickname.is)}>{u.niceName}</a> {u.firstName} {u.lastName}</li>
+
+  def calcUser: Can[User] =
+  S.attr("userId").flatMap(s => User.find(toLong(s))) or User.currentUser
+
+  def followers(in: NodeSeq): NodeSeq = 
+  <ul>
+    {
+      calcUser.toList.flatMap(_.followers.map(userFmt))
+    }
+  </ul>
+
+  def following(in: NodeSeq): NodeSeq =
+  <ul>
+    {
+      calcUser.toList.flatMap(_.following.map(userFmt))
+    }
+  </ul>
+
+  def loginForm(in: NodeSeq): NodeSeq =
+    if (User.loggedIn_?) NodeSeq.Empty
+    else User.loginForm
+  
+
+  def userName(in: NodeSeq) = {
+    if (User.currentUser.map(_.needsChange_?) openOr false)
+    S.redirectTo("/user_mgt/edit")
+    
+    Text(User.currentUser.map(_.wholeName) openOr "")
+  }
+
+  def postScript(in: NodeSeq): NodeSeq =
+  <xml:group>
+    {Script(JsonPoster.is._2)}
+    {Script(Function("post_msg", List(),
+                     JsonPoster.is._1("post",
+                                      JsObj("msg" -> ValById("textdude"),
+                                            "tags" -> ValById("tagdude"),
+                                            "reply-to" -> JsVar("currentConvNumber"))) &
+                     SetValById("textdude", "") &
+                     SetValById("tagdude", "") &
+                     JsRaw("clearReplyTo();")
+        ))
+    }
+  </xml:group>
+}

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

Added: incubator/esme/trunk/server/src/main/scala/us/esme/view/ActionView.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/view/ActionView.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/view/ActionView.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/view/ActionView.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,163 @@
+package us.esme.view
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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 js._
+import JE._
+import JsCmds._
+import SHtml._
+import util._
+import Helpers._
+import sitemap._
+import mapper._
+import Loc._
+
+import us.esme._
+import model._
+
+import scala.xml.{NodeSeq}
+
+object ActionView {
+  def loggedIn_? = User.loggedIn_?
+  
+  val ifIsLoggedIn = If(loggedIn_? _, "You must be logged in")
+
+  val menuItems =
+  Menu(Loc("actionMgt", List("action_view", "index"), "Action Management", ifIsLoggedIn)) ::
+  Nil
+}
+
+class ActionView extends LiftView {
+  def dispatch = Map("index" -> index _)
+
+
+  def index() =
+  for (user <- User.currentUser) yield {
+    val spanName = "TokenSpan"
+    val theName = "theName"
+    val theAction = "theAction"
+    val theTest = "theTest"
+
+    var testText = ""
+    var nameText = ""
+
+    def redisplayActions: JsCmd = SetHtml(spanName, displayActions)
+    def saveIt(actionText: String): JsCmd = {
+
+      val toSave = Action.create.name(nameText).user(user)
+      DB.use(toSave.connectionIdentifier) {
+        ignore =>
+        val act: Can[List[FieldError]] =
+        for (a1 <- toSave.setAction(actionText);
+             a2 <- a1.setTest(testText))
+        yield a2.validate
+
+        act match {
+          case Full(Nil) =>
+            toSave.save
+            S.notice("Action added")
+            redisplayActions &
+            SetValById(theName, "") & SetValById(theAction, "") &
+            SetValById(theTest, "")
+
+          case Full(xs) => S.error(xs); Noop
+          case Failure(msg, _, _) => S.error(msg) ; Noop
+          case _ => Noop
+        }
+      }
+    }
+
+    def displayActions: NodeSeq =
+    Action.findAll(By(Action.user, user), By(Action.removed, false),
+                   OrderBy(Action.id, Ascending)) match {
+      case Nil => Nil
+      case xs =>
+        <table>
+          <tr>
+            <td>Name</td>
+            <td>Enabled</td>
+            <td>Test</td>
+            <td>Action</td>
+            <td>Remove</td>
+          </tr>
+          {
+            xs.map(at =>
+              <tr>
+                <td>{at.name}</td>
+                <td>Enabled: {ajaxCheckbox(!at.disabled, e => {at.disabled(!e).save; Noop})}</td>
+                <td><pre>{at.testText}</pre></td>
+                <td><pre>{at.actionText}</pre></td>
+                <td>
+                  {SHtml.ajaxButton("Remove", () => {at.removed(true).save ; redisplayActions})}
+                </td>
+              </tr>)
+          }
+        </table>
+    }
+
+    <lift:surround with="default" at="content">
+
+      Manage your Actions items: <br/>
+      <span id={spanName}>
+        {
+          displayActions
+        }
+      </span>
+
+      {
+        ajaxForm(
+          <xml:group>
+            <table>
+              <tr><td colspan="3">New Action</td></tr>
+              <tr>
+                <td>Name</td>
+                <td>{text("", nameText = _) % ("id" -> theName)}</td>
+              </tr>
+              <tr>
+                <td>Test</td>
+                <td>{textarea("", testText = _) % ("id" -> theTest)}</td>
+                <td>
+                  @foo -- sender is @foo<br />
+                  day = (0,1) -- sent on Sunday or Monday<br/>
+                  #moo -- contains the #moo tag<br/>
+                  50% -- success 50% of the time<br/>
+                  @foo &amp; 50% -- half the time, something sent by @foo
+                </td>
+              </tr>
+
+              <tr>
+                <td>Action</td>
+                <td>{textarea("", saveIt) % ("id" -> theAction)}</td>
+                <td>
+                  filter -- not put in your timeline<br />
+                  resend -- sends the message to all your followers<br />
+                  mailto:foo@bar.com -- sends the message to foo@bar.com<br/>
+                  http://foo.com/message/in -- makes an HTTP post with the message
+                  </td>
+              </tr>
+              <input type="submit" value="Add" />
+            </table>
+          </xml:group>
+        )
+      }
+
+    </lift:surround>
+  }
+}

Added: incubator/esme/trunk/server/src/main/scala/us/esme/view/Auth.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/view/Auth.scala?rev=726084&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/us/esme/view/Auth.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/us/esme/view/Auth.scala Fri Dec 12 10:32:17 2008
@@ -0,0 +1,100 @@
+package us.esme.view
+
+/*
+ * Copyright 2008 WorldWide Conferencing, LLC
+ *
+ * Licensed 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 js._
+import JE._
+import JsCmds._
+import SHtml._
+import util._
+import Helpers._
+import sitemap._
+import Loc._
+
+import us.esme._
+import model._
+
+import scala.xml.{NodeSeq}
+
+object Auth {
+  def loggedIn_? = User.loggedIn_?
+  
+  val ifIsLoggedIn = If(loggedIn_? _, "You must be logged in")
+
+  val menuItems =
+  Menu(Loc("authToken", List("auth", "index"), "Manage Tokens", ifIsLoggedIn)) ::
+  Nil
+}
+
+class Auth extends LiftView {
+  def dispatch = Map("index" -> index _)
+
+  def index() =
+  for (user <- User.currentUser) yield {
+    val spanName = "TokenSpan"
+    val theInput = "theInput"
+    def redisplayTokens: JsCmd = SetHtml(spanName, displayTokens)
+
+    def addAuthToken(desc: String): JsCmd = {
+      desc.trim match {
+        case x if x.length < 3 => S.error("Description too short")
+        case x => AuthToken.create.description(x).user(user).saveMe
+          S.notice("New token added")
+      }
+
+      redisplayTokens & SetValById(theInput, "")
+    }
+
+    def displayTokens: NodeSeq =
+    user.authTokens match {
+      case Nil => Nil
+      case xs =>
+        <ul>
+          {
+            xs.map(at => <li>{at.description} token: {at.uniqueId}
+                {SHtml.ajaxButton("Revoke",
+                                  () => {at.delete_!
+                                             redisplayTokens})}</li>)
+          }
+        </ul>
+    }
+
+    <lift:surround with="default" at="content">
+
+      Manage your external application authentication tokens: <br/>
+      <span id={spanName}>
+      {
+        displayTokens
+      }
+      </span>
+
+      {
+        ajaxForm(
+          <xml:group>
+            Create a new Authentication Token.<br />
+            Description: {text("", addAuthToken) % ("id" -> theInput)}
+            <input type="submit" value="Add" />
+          </xml:group>
+        )
+      }
+
+    </lift:surround>
+  }
+}



Mime
View raw message