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]-->
+
+"""
+ )
+ }
+}