Return-Path: Delivered-To: apmail-incubator-esme-commits-archive@locus.apache.org Received: (qmail 26958 invoked from network); 12 Dec 2008 20:29:59 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 12 Dec 2008 20:29:59 -0000 Received: (qmail 57525 invoked by uid 500); 12 Dec 2008 20:30:12 -0000 Delivered-To: apmail-incubator-esme-commits-archive@incubator.apache.org Received: (qmail 57507 invoked by uid 500); 12 Dec 2008 20:30:12 -0000 Mailing-List: contact esme-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: esme-dev@incubator.apache.org Delivered-To: mailing list esme-commits@incubator.apache.org Delivered-To: moderator for esme-commits@incubator.apache.org Received: (qmail 95515 invoked by uid 99); 12 Dec 2008 18:33:08 -0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r726084 [1/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 -0000 To: esme-commits@incubator.apache.org From: dpp@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20081212183226.0797D2388886@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: dpp Date: Fri Dec 12 10:32:17 2008 New Revision: 726084 URL: http://svn.apache.org/viewvc?rev=726084&view=rev Log: Initial server commit Added: incubator/esme/trunk/server/ incubator/esme/trunk/server/pom.xml incubator/esme/trunk/server/src/ incubator/esme/trunk/server/src/main/ incubator/esme/trunk/server/src/main/resources/ incubator/esme/trunk/server/src/main/resources/props/ incubator/esme/trunk/server/src/main/resources/props/compass.cfg.xml incubator/esme/trunk/server/src/main/resources/props/dpp.props incubator/esme/trunk/server/src/main/resources/props/project.props incubator/esme/trunk/server/src/main/resources/props/sapservicec71.props incubator/esme/trunk/server/src/main/scala/ incubator/esme/trunk/server/src/main/scala/bootstrap/ incubator/esme/trunk/server/src/main/scala/bootstrap/liftweb/ incubator/esme/trunk/server/src/main/scala/bootstrap/liftweb/Boot.scala incubator/esme/trunk/server/src/main/scala/us/ incubator/esme/trunk/server/src/main/scala/us/esme/ incubator/esme/trunk/server/src/main/scala/us/esme/actor/ incubator/esme/trunk/server/src/main/scala/us/esme/actor/Distributor.scala incubator/esme/trunk/server/src/main/scala/us/esme/actor/GroupActor.scala incubator/esme/trunk/server/src/main/scala/us/esme/actor/HttpSender.scala incubator/esme/trunk/server/src/main/scala/us/esme/actor/MessagePullActor.scala incubator/esme/trunk/server/src/main/scala/us/esme/actor/UserActor.scala incubator/esme/trunk/server/src/main/scala/us/esme/api/ incubator/esme/trunk/server/src/main/scala/us/esme/api/RestAPI.scala incubator/esme/trunk/server/src/main/scala/us/esme/comet/ incubator/esme/trunk/server/src/main/scala/us/esme/comet/.keep incubator/esme/trunk/server/src/main/scala/us/esme/comet/PublicTimeline.scala incubator/esme/trunk/server/src/main/scala/us/esme/comet/TagCloud.scala incubator/esme/trunk/server/src/main/scala/us/esme/comet/Timeline.scala incubator/esme/trunk/server/src/main/scala/us/esme/external/ incubator/esme/trunk/server/src/main/scala/us/esme/external/TwitterFeed.scala incubator/esme/trunk/server/src/main/scala/us/esme/lib/ incubator/esme/trunk/server/src/main/scala/us/esme/lib/MsgFormat.scala incubator/esme/trunk/server/src/main/scala/us/esme/lib/MsgParser.scala incubator/esme/trunk/server/src/main/scala/us/esme/lib/TagUtils.scala incubator/esme/trunk/server/src/main/scala/us/esme/model/ incubator/esme/trunk/server/src/main/scala/us/esme/model/.keep incubator/esme/trunk/server/src/main/scala/us/esme/model/Action.scala incubator/esme/trunk/server/src/main/scala/us/esme/model/AuthToken.scala incubator/esme/trunk/server/src/main/scala/us/esme/model/DidPerform.scala incubator/esme/trunk/server/src/main/scala/us/esme/model/ExtSession.scala incubator/esme/trunk/server/src/main/scala/us/esme/model/Group.scala incubator/esme/trunk/server/src/main/scala/us/esme/model/Mailbox.scala incubator/esme/trunk/server/src/main/scala/us/esme/model/Message.scala incubator/esme/trunk/server/src/main/scala/us/esme/model/MessageTag.scala incubator/esme/trunk/server/src/main/scala/us/esme/model/Relationship.scala incubator/esme/trunk/server/src/main/scala/us/esme/model/Tag.scala incubator/esme/trunk/server/src/main/scala/us/esme/model/Tracking.scala incubator/esme/trunk/server/src/main/scala/us/esme/model/UrlStore.scala incubator/esme/trunk/server/src/main/scala/us/esme/model/User.scala incubator/esme/trunk/server/src/main/scala/us/esme/snippet/ incubator/esme/trunk/server/src/main/scala/us/esme/snippet/.keep incubator/esme/trunk/server/src/main/scala/us/esme/snippet/Style.scala incubator/esme/trunk/server/src/main/scala/us/esme/snippet/UserSnip.scala incubator/esme/trunk/server/src/main/scala/us/esme/view/ incubator/esme/trunk/server/src/main/scala/us/esme/view/.keep incubator/esme/trunk/server/src/main/scala/us/esme/view/ActionView.scala incubator/esme/trunk/server/src/main/scala/us/esme/view/Auth.scala incubator/esme/trunk/server/src/main/scala/us/esme/view/Track.scala incubator/esme/trunk/server/src/main/scala/us/esme/view/UserView.scala incubator/esme/trunk/server/src/main/webapp/ incubator/esme/trunk/server/src/main/webapp/WEB-INF/ incubator/esme/trunk/server/src/main/webapp/WEB-INF/web.xml incubator/esme/trunk/server/src/main/webapp/images/ incubator/esme/trunk/server/src/main/webapp/images/bg-i.png (with props) incubator/esme/trunk/server/src/main/webapp/images/bg-left.png (with props) incubator/esme/trunk/server/src/main/webapp/images/bg-m.png (with props) incubator/esme/trunk/server/src/main/webapp/images/bg-right.png (with props) incubator/esme/trunk/server/src/main/webapp/images/bg-right1.png (with props) incubator/esme/trunk/server/src/main/webapp/images/bg-t.png (with props) incubator/esme/trunk/server/src/main/webapp/images/btn-send-m.png (with props) incubator/esme/trunk/server/src/main/webapp/images/btn-sign-in.png (with props) incubator/esme/trunk/server/src/main/webapp/images/esme-blue.png (with props) incubator/esme/trunk/server/src/main/webapp/images/esme.png (with props) incubator/esme/trunk/server/src/main/webapp/images/img-1.png (with props) incubator/esme/trunk/server/src/main/webapp/images/mail.png (with props) incubator/esme/trunk/server/src/main/webapp/images/send-message.png (with props) incubator/esme/trunk/server/src/main/webapp/images/sh-a.png (with props) incubator/esme/trunk/server/src/main/webapp/images/sh-b.png (with props) incubator/esme/trunk/server/src/main/webapp/images/sh-r.png (with props) incubator/esme/trunk/server/src/main/webapp/images/sh-t.png (with props) incubator/esme/trunk/server/src/main/webapp/images/sign-on.png (with props) incubator/esme/trunk/server/src/main/webapp/index.html incubator/esme/trunk/server/src/main/webapp/scripts/ incubator/esme/trunk/server/src/main/webapp/scripts/countdown_timer.js incubator/esme/trunk/server/src/main/webapp/scripts/jquery-baf.js incubator/esme/trunk/server/src/main/webapp/scripts/jquery-cur.min.js incubator/esme/trunk/server/src/main/webapp/scripts/jquery.blockUI.js incubator/esme/trunk/server/src/main/webapp/scripts/jquery.dimensions.js incubator/esme/trunk/server/src/main/webapp/scripts/jquery.tooltips.js incubator/esme/trunk/server/src/main/webapp/scripts/json2.js incubator/esme/trunk/server/src/main/webapp/scripts/tip_balloon/ incubator/esme/trunk/server/src/main/webapp/scripts/tip_balloon.js incubator/esme/trunk/server/src/main/webapp/scripts/tip_balloon/b.gif (with props) incubator/esme/trunk/server/src/main/webapp/scripts/tip_balloon/background.gif (with props) incubator/esme/trunk/server/src/main/webapp/scripts/tip_balloon/l.gif (with props) incubator/esme/trunk/server/src/main/webapp/scripts/tip_balloon/lb.gif (with props) incubator/esme/trunk/server/src/main/webapp/scripts/tip_balloon/lt.gif (with props) incubator/esme/trunk/server/src/main/webapp/scripts/tip_balloon/r.gif (with props) incubator/esme/trunk/server/src/main/webapp/scripts/tip_balloon/rb.gif (with props) incubator/esme/trunk/server/src/main/webapp/scripts/tip_balloon/rt.gif (with props) incubator/esme/trunk/server/src/main/webapp/scripts/tip_balloon/stemb.gif (with props) incubator/esme/trunk/server/src/main/webapp/scripts/tip_balloon/stemt.gif (with props) incubator/esme/trunk/server/src/main/webapp/scripts/tip_balloon/t.gif (with props) incubator/esme/trunk/server/src/main/webapp/scripts/tip_centerwindow.js incubator/esme/trunk/server/src/main/webapp/scripts/tip_followscroll.js incubator/esme/trunk/server/src/main/webapp/scripts/ui.accordion.js incubator/esme/trunk/server/src/main/webapp/scripts/ui.dialog.js incubator/esme/trunk/server/src/main/webapp/scripts/ui.draggable.js incubator/esme/trunk/server/src/main/webapp/scripts/ui.mouse.js incubator/esme/trunk/server/src/main/webapp/scripts/ui.resizable.js incubator/esme/trunk/server/src/main/webapp/scripts/ui.tabs.js incubator/esme/trunk/server/src/main/webapp/scripts/wz_tooltip.js incubator/esme/trunk/server/src/main/webapp/static/ incubator/esme/trunk/server/src/main/webapp/static/about.html incubator/esme/trunk/server/src/main/webapp/style/ incubator/esme/trunk/server/src/main/webapp/style/b-back.css incubator/esme/trunk/server/src/main/webapp/style/b-content.css incubator/esme/trunk/server/src/main/webapp/style/b-edit.css incubator/esme/trunk/server/src/main/webapp/style/b-list-ie.css incubator/esme/trunk/server/src/main/webapp/style/b-list.css incubator/esme/trunk/server/src/main/webapp/style/b-menu.css incubator/esme/trunk/server/src/main/webapp/style/b-open.css incubator/esme/trunk/server/src/main/webapp/style/b-popup-c.css incubator/esme/trunk/server/src/main/webapp/style/b-popup-ie.css incubator/esme/trunk/server/src/main/webapp/style/b-popup.css incubator/esme/trunk/server/src/main/webapp/style/b-primary.css incubator/esme/trunk/server/src/main/webapp/style/b-sign-on.css incubator/esme/trunk/server/src/main/webapp/style/b-view-ie.css incubator/esme/trunk/server/src/main/webapp/style/b-view-menu.css incubator/esme/trunk/server/src/main/webapp/style/b-view.css incubator/esme/trunk/server/src/main/webapp/style/esme-ie.css incubator/esme/trunk/server/src/main/webapp/style/esme.css incubator/esme/trunk/server/src/main/webapp/style/flora/ incubator/esme/trunk/server/src/main/webapp/style/flora/flora.accordion.css incubator/esme/trunk/server/src/main/webapp/style/flora/flora.all.css incubator/esme/trunk/server/src/main/webapp/style/flora/flora.calendar.css incubator/esme/trunk/server/src/main/webapp/style/flora/flora.css incubator/esme/trunk/server/src/main/webapp/style/flora/flora.dialog.css incubator/esme/trunk/server/src/main/webapp/style/flora/flora.menu.css incubator/esme/trunk/server/src/main/webapp/style/flora/flora.resizable.css incubator/esme/trunk/server/src/main/webapp/style/flora/flora.shadow.css incubator/esme/trunk/server/src/main/webapp/style/flora/flora.slider.css incubator/esme/trunk/server/src/main/webapp/style/flora/flora.tablesorter.css incubator/esme/trunk/server/src/main/webapp/style/flora/flora.tabs.css incubator/esme/trunk/server/src/main/webapp/style/flora/i/ incubator/esme/trunk/server/src/main/webapp/style/flora/i/accordion-left-act.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/accordion-left-over.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/accordion-left.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/accordion-middle-act.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/accordion-middle-over.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/accordion-middle.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/accordion-right-act.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/accordion-right-over.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/accordion-right.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/asc.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/bg.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/desc.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/dialog-e.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/dialog-n.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/dialog-ne.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/dialog-nw.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/dialog-s.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/dialog-se.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/dialog-sw.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/dialog-title.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/dialog-titlebar-close-hover.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/dialog-titlebar-close.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/dialog-w.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/menu-submenu.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/resizable-e.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/resizable-n.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/resizable-ne.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/resizable-nw.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/resizable-s.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/resizable-se.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/resizable-sw.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/resizable-w.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/shadow.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/slider-bg-1.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/slider-bg-2.png (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/slider-handle.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/flora/i/tabs.gif (with props) incubator/esme/trunk/server/src/main/webapp/style/l-page-content.css incubator/esme/trunk/server/src/main/webapp/style/l-page-login.css incubator/esme/trunk/server/src/main/webapp/style/l-page-message.css incubator/esme/trunk/server/src/main/webapp/style/l-page.css incubator/esme/trunk/server/src/main/webapp/style/l-top-ie.css incubator/esme/trunk/server/src/main/webapp/style/l-top.css incubator/esme/trunk/server/src/main/webapp/style/layout.css incubator/esme/trunk/server/src/main/webapp/style/print-ie.css incubator/esme/trunk/server/src/main/webapp/style/print.css incubator/esme/trunk/server/src/main/webapp/style/ui.tabs.css incubator/esme/trunk/server/src/main/webapp/templates-hidden/ incubator/esme/trunk/server/src/main/webapp/templates-hidden/default.html incubator/esme/trunk/server/src/main/webapp/templates-hidden/login.html incubator/esme/trunk/server/src/main/webapp/templates-hidden/menu_footer.html incubator/esme/trunk/server/src/main/webapp/templates-hidden/message.html incubator/esme/trunk/server/src/test/ incubator/esme/trunk/server/src/test/scala/ incubator/esme/trunk/server/src/test/scala/LiftConsole.scala incubator/esme/trunk/server/src/test/scala/RunWebApp.scala incubator/esme/trunk/server/src/test/scala/us/ incubator/esme/trunk/server/src/test/scala/us/esme/ incubator/esme/trunk/server/src/test/scala/us/esme/AppTest.scala incubator/esme/trunk/server/src/test/scala/us/esme/lib/ incubator/esme/trunk/server/src/test/scala/us/esme/lib/MsgParseTest.scala Added: incubator/esme/trunk/server/pom.xml URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/pom.xml?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/pom.xml (added) +++ incubator/esme/trunk/server/pom.xml Fri Dec 12 10:32:17 2008 @@ -0,0 +1,230 @@ + + + 4.0.0 + us.esme + esme + 0.2.3-SNAPSHOT + war + ESME + 2008 + + 2.7.2 + 2.3.2 + gfv3 + + + + + scala-tools.org + Scala-Tools Maven2 Repository + http://scala-tools.org/repo-releases + + + scala-tools.org.snapshots + Scala-Tools Maven2 Repository for Snapshots + http://scala-tools.org/repo-snapshots + + + + compass-project.org + Compass + http://repo.compass-project.org + + + + + + scala-tools.org + Scala-Tools Maven2 Repository + http://scala-tools.org/repo-releases + + + + + + org.scala-lang + scala-library + ${scala.version} + + + net.liftweb + lift-core + 0.10-SNAPSHOT + + + net.liftweb + lift-testkit + 0.10-SNAPSHOT + + + net.liftweb + lift-openid + 0.10-SNAPSHOT + + + + org.compass-project + compass + 2.0.2 + + + + postgresql + postgresql + 8.2-507.jdbc3 + + + org.apache.derby + derby + 10.2.2.0 + + + javax.servlet + servlet-api + 2.5 + provided + + + junit + junit + 3.8.1 + test + + + cglib + cglib + 2.1_3 + test + + + org.objenesis + objenesis + 1.0 + test + + + net.sourceforge.jwebunit + jwebunit-htmlunit-plugin + 1.4.1 + test + + + javax.servlet + servlet-api + + + + + org.mortbay.jetty + jetty + [6.1.6,) + test + + + + org.scala-lang + scala-compiler + ${scala.version} + test + + + org.apache.lucene + lucene-snowball + ${lucene.version} + + + org.specs + specs + 1.3.1 + test + + + junit + junit + 4.4 + test + + + + + src/main/scala + src/test/scala + + + + org.scala-tools + maven-scala-plugin + + + + compile + testCompile + + + + + ${scala.version} + + + + org.mortbay.jetty + maven-jetty-plugin + + / + 0 + + + + net.sf.alchim + yuicompressor-maven-plugin + + + + compress + + + + + true + + + + org.apache.maven.plugins + maven-eclipse-plugin + + true + + org.scala-lang:scala-library + + + ch.epfl.lamp.sdt.launching.SCALA_CONTAINER + + + ch.epfl.lamp.sdt.core.scalanature + org.eclipse.jdt.core.javanature + + + ch.epfl.lamp.sdt.core.scalabuilder + + + + + + + + + org.scala-tools + maven-scala-plugin + + ${scala.version} + + + + + Added: incubator/esme/trunk/server/src/main/resources/props/compass.cfg.xml URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/resources/props/compass.cfg.xml?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/resources/props/compass.cfg.xml (added) +++ incubator/esme/trunk/server/src/main/resources/props/compass.cfg.xml Fri Dec 12 10:32:17 2008 @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Added: incubator/esme/trunk/server/src/main/resources/props/dpp.props URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/resources/props/dpp.props?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/resources/props/dpp.props (added) +++ incubator/esme/trunk/server/src/main/resources/props/dpp.props Fri Dec 12 10:32:17 2008 @@ -0,0 +1 @@ +use_local_psql=true Added: incubator/esme/trunk/server/src/main/resources/props/project.props URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/resources/props/project.props?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/resources/props/project.props (added) +++ incubator/esme/trunk/server/src/main/resources/props/project.props Fri Dec 12 10:32:17 2008 @@ -0,0 +1,4 @@ +# Properties file for COIL servers (user id = project) +http.proxyHost=vsvGW000.pal.coil +http.proxyPort=8080 +http.nonProxyHosts=*.sap.corp Added: incubator/esme/trunk/server/src/main/resources/props/sapservicec71.props URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/resources/props/sapservicec71.props?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/resources/props/sapservicec71.props (added) +++ incubator/esme/trunk/server/src/main/resources/props/sapservicec71.props Fri Dec 12 10:32:17 2008 @@ -0,0 +1,4 @@ +# Properties file for COIL servers (user id = project) +http.proxyHost=vsvGW000.pal.coil +http.proxyPort=8080 +http.nonProxyHosts=*.sap.corp Added: incubator/esme/trunk/server/src/main/scala/bootstrap/liftweb/Boot.scala URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/bootstrap/liftweb/Boot.scala?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/scala/bootstrap/liftweb/Boot.scala (added) +++ incubator/esme/trunk/server/src/main/scala/bootstrap/liftweb/Boot.scala Fri Dec 12 10:32:17 2008 @@ -0,0 +1,213 @@ +package bootstrap.liftweb + +/* + * 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.util._ +import net.liftweb.http._ +import net.liftweb.sitemap._ +import net.liftweb.sitemap.Loc._ +import Helpers._ +import net.liftweb.mapper.{DB, ConnectionManager, Schemifier, DefaultConnectionIdentifier, ConnectionIdentifier} +import java.sql.{Connection, DriverManager} +import us.esme._ +import model._ +import actor._ +import lib._ +import view._ +import api._ +import net.liftweb._ +import mapper._ +import javax.servlet.http.{HttpServlet, HttpServletRequest , HttpServletResponse, HttpSession} +import org.compass.core._ +import org.compass.core.config.CompassConfiguration + +/** + * A class that's instantiated early and run. It allows the application + * to modify lift's environment + */ +class Boot { + def boot { + DefaultConnectionIdentifier.jndiName = "esme" + + if (!DB.jndiJdbcConnAvailable_?) DB.defineConnectionManager(DefaultConnectionIdentifier, DBVendor) + // where to search snippet + LiftRules.addToPackages("us.esme") + + if (Props.mode == Props.RunModes.Test) { + Schemifier.destroyTables_!!(Log.infoF _, User, ExtSession, + Message, Mailbox, Tag, + Group, Relationship, MessageTag, + AuthToken, UrlStore, Tracking, + Action, DidPerform) + } + + Schemifier.schemify(true, Log.infoF _, User, ExtSession, Message, + Mailbox, Tag, + Group, Relationship, MessageTag, AuthToken, + UrlStore, Tracking, Action, DidPerform) + + LiftRules.prependTemplate(User.templates) + + LiftRules.appendStatelessDispatch { + case r @ Req("api" :: "send_msg" :: Nil, "", PostRequest) + if r.param("token").isDefined => + () => RestAPI.sendMsgWithToken(r) + } + + LiftRules.appendDispatch(ESMEOpenIDVendor.dispatchPF) + + LiftRules.siteMapFailRedirectLocation = List("static", "about") + + LiftRules.prependRewrite { + case RewriteRequest(ParsePath("user" :: user :: Nil,"", _,_), _, _) => + RewriteResponse( List("user_view", "index"), Map("uid" -> user)) + case RewriteRequest(ParsePath("tag" :: tag :: Nil,"", _,_), _, _) => + RewriteResponse( List("user_view", "tag"), Map("tag" -> tag)) + + case RewriteRequest(ParsePath("conversation" :: cid :: Nil, "", _, _), + _, _) => + RewriteResponse(List("user_view", "conversation"), Map("cid" -> cid)) + + case RewriteRequest(ParsePath("search" :: term :: Nil,"", _,_), _, _) => + RewriteResponse( List("user_view", "search"), Map("term" -> term)) + } + + LiftRules.appendDispatch(UrlStore.redirectizer) + + + LiftRules.siteMapFailRedirectLocation = List("static", "about") + + // Build SiteMap + val entries = Menu(Loc("Home", List("index"), "Home")) :: + Menu(Loc("list_users", List("user_view", "all"), "List Users")) :: + Menu(Loc("user", List("user_view", "index"), "User Info", Hidden)) :: + Menu(Loc("conv", List("user_view", "conversation"), "Conversation", Hidden)) :: + Menu(Loc("about", List("static", "about"), "About", Hidden)) :: + Menu(Loc("tag", List("user_view", "tag"), "Tag", Hidden)) :: + Menu(Loc("search", List("user_view", "search"), "Search", Hidden)) :: + User.sitemap ::: + Track.menuItems ::: + Auth.menuItems ::: + ActionView.menuItems + + LiftRules.setSiteMap(SiteMap(entries:_*)) + S.addAround(User.requestLoans) + S.addAround(ExtSession.requestLoans) + + LiftRules.appendViewDispatch { + case "user_view" :: _ => UserView + } + + LiftRules.prependDispatch(RestAPI.dispatch) + + LiftRules.appendEarly(makeUtf8) + + Distributor.touch + + DB.addLogFunc(S.logQuery _) + S.addAnalyzer(RequestAnalyzer.analyze _) + } + private def makeUtf8(req: HttpServletRequest): Unit = {req.setCharacterEncoding("UTF-8")} +} + +object Compass { + // Set up Compass for search + val conf = new CompassConfiguration().configure("/props/compass.cfg.xml").addClass((new Message).clazz) + val compass = conf.buildCompass() + if (!compass.getSearchEngineIndexManager.indexExists) + { + // Create Index if one does not exist + compass.getSearchEngineIndexManager().createIndex() + } +} + + +object RequestAnalyzer { + + def analyze(req: Can[Req], time: Long, queries: List[(String, Long)]): Unit = { + val longQueries = (queries.filter(_._2 > 30)) + if (time > 50 || longQueries.?) { + Log.info("Request "+req.map(_.uri).openOr("No Request")+ + " took "+time+" query "+longQueries.comma) + } + } +} + +object DBVendor extends ConnectionManager { + private var pool: List[Connection] = Nil + private var poolSize = 0 + private val maxPoolSize = 4 + + private def createOne: Can[Connection] = try { + if (Props.getBool("use_prod_psql", false)) { + Class.forName("org.postgresql.Driver") + val dm = DriverManager. + getConnection("jdbc:postgresql://localhost/esme_prod", + Props.get("db_user", "dpp"), + Props.get("db_pwd", "")) + Full(dm) + } else if (Props.getBool("use_local_psql", false)) { + Class.forName("org.postgresql.Driver") + val dm = DriverManager. + getConnection("jdbc:postgresql://localhost/esme_dev", + Props.get("db_user", "dpp"), + Props.get("db_pwd", "")) + Full(dm) + } else { + Class.forName("org.apache.derby.jdbc.EmbeddedDriver") + val driverName = Props.mode match { + case Props.RunModes.Test => "jdbc:derby:esme_test_db;create=true" + case _ => "jdbc:derby:esme_db;create=true" + } + + val dm = DriverManager.getConnection(driverName) + Full(dm) + } + } catch { + case e : Exception => e.printStackTrace; Empty + } + + def newConnection(name: ConnectionIdentifier): Can[Connection] = synchronized { + pool match { + case Nil if poolSize < maxPoolSize => val ret = createOne + poolSize = poolSize + 1 + ret.foreach(c => pool = c :: pool) + ret + + case Nil => wait(1000L); newConnection(name) + case x :: xs => try { + x.setAutoCommit(false) + Full(x) + } catch { + case e => try { + pool = xs + poolSize = poolSize - 1 + x.close + newConnection(name) + } catch { + case e => newConnection(name) + } + } + } + } + + def releaseConnection(conn: Connection): Unit = synchronized { + pool = conn :: pool + notify + } +} + Added: incubator/esme/trunk/server/src/main/scala/us/esme/actor/Distributor.scala URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/actor/Distributor.scala?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/scala/us/esme/actor/Distributor.scala (added) +++ incubator/esme/trunk/server/src/main/scala/us/esme/actor/Distributor.scala Fri Dec 12 10:32:17 2008 @@ -0,0 +1,143 @@ +/* + * 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. + */ + +/* + * Distributor.scala + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package us.esme.actor + +import scala.actors.Actor +import Actor._ + +import net.liftweb._ +import http._ +import util._ + +import us.esme._ +import model._ + +import scala.xml.{Elem} + +object Distributor extends Actor { + def act = loop { + react { + case StartMeUp => + link(ActorWatcher) + User.findAll.map(_.id.is).foreach(findOrCreateUser) + Group.findAll.map(_.id.is).foreach(findOrCreateGroup) + + case UserCreatedMessage(user, text, tags, when, + metaData, + source, + inReplyTo) => + findOrCreateUser(user) ! + UserActor.CreateMessage(text, tags, + when, metaData, source, inReplyTo) + + case AddMessageToMailbox(user, message, reason) => + findOrCreateUser(user) ! UserActor.AddToMailbox(message, reason) + + case Listen(user, who) => + findOrCreateUser(user) ! UserActor.Listen(who) + + case Unlisten(user, who) => + findOrCreateUser(user) ! UserActor.Unlisten(who) + + case LatestMessages(user, cnt) => + findOrCreateUser(user).forward(UserActor.LatestMessages(cnt)) + + case m @ UserUpdated(id) => + users.get(id).foreach(_ ! m) + + case nm @ NewMessage(msg) => + val toSend = UserActor.TestForTracking(msg) + users.values.foreach(_ ! toSend) + listeners.foreach(_ ! nm) + + case UpdateTrackingFor(userId, ttype) => + for (user <- users.get(userId)) + user ! UserActor.UpdateTracking(ttype) + + case PublicTimelineListeners(who) => + listeners = who :: listeners + + case PublicTimelineUnlisteners(who) => + listeners = listeners.filter(_ ne who) + + case _ => + } + } + + private case object StartMeUp + + case class UserCreatedMessage(user: Long, text: String, tags: List[String], + when: Long, + metaData: Can[Elem], + source: String, + replyTo: Can[Long]) + case class AddMessageToMailbox(user: Long, message: Message, reason: MailboxReason) + case class Listen(user: Long, who: Actor) + case class Unlisten(user: Long, who: Actor) + case class LatestMessages(user: Long, cnt: Int) + case class NewMessage(msg: Message) + case class UpdateTrackingFor(user: Long, which: TrackingType) + case class UserUpdated(userId: Long) + case class PublicTimelineListeners(who: Actor) + case class PublicTimelineUnlisteners(who: Actor) + sealed trait TrackingType + case object PerformTrackingType extends TrackingType + case object TrackTrackingType extends TrackingType + + start + this ! StartMeUp + + // do nothing + def touch { + + } + + private var users: Map[Long, UserActor] = Map.empty + private var groups: Map[Long, GroupActor] = Map.empty + private var listeners: List[Actor] = Nil + + private def findOrCreateUser(user: Long): UserActor = { + users.get(user) match { + case Some(ret) => ret + case _ => + val ret = new UserActor + ret.start + ret ! UserActor.StartMeUp(user) + users = users + (user -> ret) + ret + } + } + + private def findOrCreateGroup(group: Long) { + groups.get(group) match { + case Some(ret) => ret + case _ => + val ret = new GroupActor + ret.start + ret ! GroupActor.StartMeUp(group) + groups = groups + (group -> ret) + ret + } + } +} Added: incubator/esme/trunk/server/src/main/scala/us/esme/actor/GroupActor.scala URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/actor/GroupActor.scala?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/scala/us/esme/actor/GroupActor.scala (added) +++ incubator/esme/trunk/server/src/main/scala/us/esme/actor/GroupActor.scala Fri Dec 12 10:32:17 2008 @@ -0,0 +1,42 @@ +/* + * 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.actor + +import scala.actors.Actor +import Actor._ + + +import net.liftweb._ +import http._ + +import us.esme._ +import model._ + +object GroupActor { + case class StartMeUp(group: Long) +} + +class GroupActor extends Actor { + import GroupActor._ + + def act = loop { + react { + case StartMeUp => + link(ActorWatcher) + } + } +} Added: incubator/esme/trunk/server/src/main/scala/us/esme/actor/HttpSender.scala URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/actor/HttpSender.scala?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/scala/us/esme/actor/HttpSender.scala (added) +++ incubator/esme/trunk/server/src/main/scala/us/esme/actor/HttpSender.scala Fri Dec 12 10:32:17 2008 @@ -0,0 +1,82 @@ +/* + * 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.actor + +import scala.actors.Actor +import Actor._ + +import net.liftweb._ +import http._ +import util._ +import net.liftweb.http.testing._ + +import us.esme._ +import model._ +import lib._ + +import org.apache.commons.httpclient._ + +object HttpSender extends Actor with GetPoster { + def act = loop { + react { + case StartMeUp => + link(ActorWatcher) + + + case SendAMessage(action, msg, token) => + send(action, msg, token) + + case _ => + } + } + + private case object StartMeUp + case class SendAMessage(action: Performances, msg: Message, token: String) + + private def send(action: Performances, msg: Message, token: String) { + import Mailer._ + + action match { + case MailTo(who) => Mailer.sendMail(From("i@esme.us"), Subject("msg"), + To(who), + XHTMLMailBodyType(msg.digestedXHTML)) + case HttpTo(url, headers) => + post(url, httpClient, + ("X-ESME-Token" -> token) :: headers, + msg.toXml) + + case PerformResend | PerformFilter => // IGNORE + + } + } + + def httpClient = { + val ret = new HttpClient(new SimpleHttpConnectionManager(false)) + + for (host <- Props.get("http.proxyHost")) + ret.getHostConfiguration.setProxy(host, + Props.getInt("http.proxyPort",80)) + ret + } + + start + this ! StartMeUp + + def baseUrl = "" +} Added: incubator/esme/trunk/server/src/main/scala/us/esme/actor/MessagePullActor.scala URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/actor/MessagePullActor.scala?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/scala/us/esme/actor/MessagePullActor.scala (added) +++ incubator/esme/trunk/server/src/main/scala/us/esme/actor/MessagePullActor.scala Fri Dec 12 10:32:17 2008 @@ -0,0 +1,55 @@ +package us.esme.actor + +/* + * 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 scala.actors.Actor +import scala.actors.TIMEOUT +import scala.actors.Actor._ +import us.esme.actor.Distributor.{UserCreatedMessage=>Msg} + +class MessagePullActor(val messageProcessor: Actor, val refreshSeconds: Int, private var lastMessage: Option[Msg], val messageSource: UniqueMessageSource) extends Actor { + + def act { + loop { + reactWithin (refreshSeconds * 1000) { + case (msgs: List[Msg]) => { + val lastMessages = messageSource.getLastSortedMessages(msgs, lastMessage) + for (message <- lastMessages) { + messageProcessor ! message + lastMessage = Some(message) + } + } + case TIMEOUT => actor { + // "this" used to reference invoking actor + this ! messageSource() + } + } + } + } + +} + +trait UniqueMessageSource extends (() => List[Msg]) { + def messageSorter(a: Msg, b: Msg) = a.when < b.when + + def getLastSortedMessages(msgs: List[Msg], lastMessage: Option[Msg]): List[Msg] = { + lastMessage match { + case Some(message: Msg) => msgs.filter{messageSorter(message, _)} + case None => msgs + } + }.sort(messageSorter) +} Added: incubator/esme/trunk/server/src/main/scala/us/esme/actor/UserActor.scala URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/actor/UserActor.scala?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/scala/us/esme/actor/UserActor.scala (added) +++ incubator/esme/trunk/server/src/main/scala/us/esme/actor/UserActor.scala Fri Dec 12 10:32:17 2008 @@ -0,0 +1,213 @@ +/* + * 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.actor + +import scala.actors.Actor +import Actor._ + +import net.liftweb._ +import http._ +import util._ +import mapper._ + +import us.esme._ +import model._ +import lib._ + +import java.util.logging._ +import java.util.{TimeZone, Calendar} +import scala.xml.{Elem} + +object UserActor { + private[actor] case class StartMeUp(user: Long) + private[actor] case class CreateMessage(text: String, tags: List[String], + when: Long, metaData: Can[Elem], + source: String, + replyTo: Can[Long]) + private[actor] case class AddToMailbox(msg: Message, reason: MailboxReason) + private[actor] case class Listen(who: Actor) + private[actor] case class Unlisten(who: Actor) + private[actor] case class LatestMessages(cnt: Int) + private[actor] case class TestForTracking(msg: Message) + private[actor] case class UpdateTracking(ttype: Distributor.TrackingType) + + case class MessageReceived(msg: Message, reason: MailboxReason) + + val logger: Logger = Logger.getLogger("us.esme.actor") +} + +class UserActor extends Actor { + import UserActor._ + + private var userId: Long = 0 + + private var userTimezone: TimeZone = _ + + private var listeners: List[Actor] = Nil + + private var tracking: List[TrackingMatcher] = Nil + + private var perform: List[PerformMatcher] = Nil + + private var _mailbox: Array[Long] = Array() + + private def followers: List[Long] = User.followerIdsForUserId(userId) + + private case class RunFunc(f: () => Unit) + + def act = loop { + react { + case m @ Distributor.UserUpdated(_) => + User.find(userId). + foreach(u => userTimezone = TimeZone.getTimeZone(u.timezone)) + listeners.foreach(_ ! m) + + case StartMeUp(user) => + link(ActorWatcher) + userId = user + User.find(userId). + foreach(u => userTimezone = TimeZone.getTimeZone(u.timezone)) + + _mailbox = Mailbox.mostRecentMessagesFor(userId, 500). + map(_._1.id.is).toArray + + this ! UpdateTracking(Distributor.TrackTrackingType) + this ! UpdateTracking(Distributor.PerformTrackingType) + + case RunFunc(f) => f() + + case CreateMessage(text, tags, when, metaData, source, replyTo) => + val tagLst = tags.removeDuplicates.map(Tag.findOrCreate) + + Message.create.author(userId).when(when). + source(source). + setTextAndTags(text, tagLst, metaData).map{msg => + // do some security... only reply to messages + // that are in our mailbox + for (rt <- replyTo; + mb <- Mailbox.find(By(Mailbox.message, rt), + By(Mailbox.user, userId))) msg.replyTo(rt) + + msg.saveMe + + Distributor ! Distributor.AddMessageToMailbox(userId, msg, NoReason) + + for (id <- followers) + Distributor ! Distributor.AddMessageToMailbox(id, msg, NoReason) + + for (id <- msg.sentToIds) + Distributor ! Distributor.AddMessageToMailbox(id, msg, + DirectReason(userId)) + + for (convId <- msg.conversation.can ; + val msgId = Message.findMap(By(Message.conversation, convId)) + (m => Full(m.id.is)); + userId <- (Mailbox.findMap(InRaw(Mailbox.message, msgId.mkString(", "), + IHaveValidatedThisSQL("dpp", "Aug 27. 2008"))) + (mb => Full(mb.user.is))).removeDuplicates) + Distributor ! Distributor.AddMessageToMailbox(userId, msg, ConversationReason(convId)) + + Distributor ! Distributor.NewMessage(msg) + } + + case AddToMailbox(msg, reason) => + addToMailbox(msg, reason) + + + case TestForTracking(msg) => + for (t <- tracking.find(_.doesMatch_?(msg))) + this ! AddToMailbox(msg, TrackReason(t.trackId)) + + + case UpdateTracking(ttype) => + ttype match { + case Distributor.TrackTrackingType => + tracking = Tracking.findAll(By(Tracking.user, userId), + By(Tracking.disabled, false), + By(Tracking.removed, false)). + flatMap(_.matcher) + + case Distributor.PerformTrackingType => + perform = Action.findAll(By(Action.user, userId), + By(Action.disabled, false), + By(Action.removed, false)). + map(_.matcher) + } + + case Listen(who) => listeners = who :: listeners + + case Unlisten(who) => listeners = listeners.filter(_ ne who) + + case LatestMessages(cnt) => reply(_mailbox.take(cnt).toList) + } + } + + def buildCalendar = userTimezone match { + case null => Calendar.getInstance() + case tz => Calendar.getInstance(tz) + } + + private def addToMailbox(msg: Message, reason: MailboxReason) { + // if the message is not in my mailbox + if (Mailbox.find(By(Mailbox.message, msg), + By(Mailbox.user, userId)).isEmpty) { + + // get all the performance things + val cal = buildCalendar + val toDo = perform.filter(_.func(msg, userId, cal)) + + // is one of those reasons rejection of the message + val reject = toDo.exists(_.filter_?) + + // if we're not rejecting the message + if (!reject) { + val mb = Mailbox.create.user(userId).message(msg) + reason match { + case TrackReason(trackId) => mb.viaTrack(trackId) + case DirectReason(fromId) => mb.directlyFrom(fromId) + case ConversationReason(convId) => mb.conversation(convId) + case ResendReason(resender) => mb.resentBy(resender) + case NoReason => + } + mb.saveMe + + _mailbox = (msg.id.is :: _mailbox.toList).take(500).toArray + + listeners.foreach(_ ! MessageReceived(msg, reason)) + + toDo.foreach { + td => + + td.whatToDo match { + case m @ MailTo(_) => + HttpSender ! HttpSender.SendAMessage(m, msg, td.uniqueId) + + case h @ HttpTo(_, _) => + HttpSender ! HttpSender.SendAMessage(h, msg, td.uniqueId) + + case PerformResend => + for (id <- followers) + Distributor ! + Distributor.AddMessageToMailbox(id, msg, ResendReason(userId)) + + case PerformFilter => // IGNORE + } + } + } + } + } +} Added: incubator/esme/trunk/server/src/main/scala/us/esme/api/RestAPI.scala URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/api/RestAPI.scala?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/scala/us/esme/api/RestAPI.scala (added) +++ incubator/esme/trunk/server/src/main/scala/us/esme/api/RestAPI.scala Fri Dec 12 10:32:17 2008 @@ -0,0 +1,402 @@ +/* + * 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. + */ +/* + * RestAPI.scala + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package us.esme.api + +import net.liftweb._ +import http._ +import rest._ +import util._ +import mapper._ +import Helpers._ + +import us.esme._ +import model._ +import actor._ + +import scala.xml.{NodeSeq, Text, Elem, XML} +import scala.actors.Actor +import Actor._ + +import scala.collection.mutable.ListBuffer +import java.util.logging._ + +object RestAPI extends XMLApiHelper { + val logger: Logger = Logger.getLogger("us.esme.api") + + def dispatch: LiftRules.DispatchPF = { + case Req("api" :: "status" :: Nil, "", GetRequest) => status + case Req("api" :: "login" :: Nil, "", PostRequest) => login + case Req("api" :: "logout" :: Nil, "", GetRequest) => logout + case Req("api" :: "get_msgs" :: Nil, "", GetRequest) => getMsgs + case Req("api" :: "wait_for_msgs" :: Nil, "", GetRequest) => + waitForMsgs + + case Req("api" :: "send_msg" :: Nil, "", PostRequest) => + () => sendMsg(User.currentUser.map(_.id.is), S) + + case Req("api" :: "get_following" :: Nil, _, GetRequest) => + following(calcUser) + case Req("api" :: "get_followers" :: Nil, _, GetRequest) => + followers(calcUser) + case Req("api" :: "follow" :: Nil, _, PostRequest) => + performFollow(S.param("user")) + case Req("api" :: "unfollow" :: Nil, _, PostRequest) => + performUnfollow(S.param("user")) + case Req("api" :: "all_users" :: + Nil, _, GetRequest) => allUsers _ + case Req("api" :: "get_tagcloud" :: Nil, "", GetRequest) => + () => getTagCloud(S) + + // DPP to document + case Req("api" :: "get_tracking" :: Nil, "", GetRequest) => + getTracking + + case Req("api" :: "add_tracking" :: Nil, "", PostRequest) => + addTracking + + case Req("api" :: "remove_tracking" :: Nil, "", PostRequest) => + removeTracking + + case Req("api" :: "get_conversation" :: Nil, "", GetRequest) => + getConversation + + case Req("api" :: "get_actions" :: Nil, "", GetRequest) => + getActions _ + + case Req("api" :: "add_action" :: Nil, "", PostRequest) => + addAction _ + + case Req("api" :: "enable_action" :: Nil, "", PostRequest) => + enableAction _ + + case Req("api" :: "delete_action" :: Nil, "", PostRequest) => + deleteAction _ + } + + def findAction: Can[Action] = + for (user <- User.currentUser ?~ "Not Logged In"; + id <- S.param("actionid") ?~ "id param not supplied"; + action <- Action.find(By(Action.user, user), + By(Action.id, id.toLong), + By(Action.removed, false))) yield action + + def addAction(): LiftResponse = { + val ret: Can[NodeSeq] = + for (user <- User.currentUser ?~ "Not logged in"; + name <- S.param("name") ?~ "'name' param not supplied"; + test <- S.param("test") ?~ "'test' param not supplied"; + action <- S.param("action") ?~ "'action' param not supplied"; + val a = Action.create.user(user).name(name); + a2 <- a.setTest(test); + a3 <- a.setAction(action)) yield a3.saveMe.toXml + + ret + } + + def getActions(): LiftResponse = { + val ret: Can[NodeSeq] = + for (user <- User.currentUser ?~ "Not logged in") + yield user.performing.flatMap(_.toXml) + + ret + } + + def enableAction(): LiftResponse = { + val ret: Can[Boolean] = + for (action <- findAction; + enabled <- S.param("enabled").map(toBoolean) ?~ "enabled param not supplied") + yield action.disabled(!enabled).save + + ret + } + + def deleteAction(): LiftResponse = { + val ret: Can[Boolean] = + for (action <- findAction) + yield action.removed(true).save + + ret + } + + private def calcUser: Can[User] = + S.param("user").flatMap(User.findFromWeb) or + User.currentUser + + def getTracking(): LiftResponse = { + val ret: Can[NodeSeq] = + for (user <- User.currentUser ?~ "Not logged in") + yield Tracking.findAll(By(Tracking.user, user)).flatMap(_.toXml) + ret + } + + def getConversation(): LiftResponse = { + val ret: Can[NodeSeq] = + for (user <- User.currentUser ?~ "Not logged in"; + id <- S.param("conversationid").map(toLong) ?~ "id param missing" + ) yield { + Message.findAndPrime(By(Message.conversation, id), + OrderBy(Message.id, Ascending)).map(_.toXml) + } + + ret + } + + def removeTracking(): LiftResponse = { + val ret: Can[Boolean] = + for (user <- User.currentUser ?~ "Not logged in"; + id <- S.param("trackid") ?~ "id param missing"; + track <- Tracking.find(By(Tracking.id, id.toLong), + By(Tracking.user, user)) ?~ "Couldn't find tracking item" + ) yield track.removed(true).save + + ret + } + + def addTracking(): LiftResponse = { + val ret: Can[Boolean] = + for (user <- User.currentUser ?~ "Not logged in"; + toTrack <- (S.param("track") ?~ "No track param") if toTrack.trim.length > 0) + yield + Tracking.create.user(user).regex(toTrack).save + + ret + } + + def status(): LiftResponse = + { + val ret: Can[NodeSeq] = User.currentUser.map(_.toXml) + ret + } + + def allUsers(): LiftResponse = + for (user <- User.findAll) yield user.toXml + + + def following(muser: Can[User])(): LiftResponse = { + val r: Can[NodeSeq] = for (user <- muser) yield + user.following().map(_.toXml) + + r + } + + def followers(muser: Can[User])(): LiftResponse = { + val r: Can[NodeSeq] = for (user <- muser) yield + user.followers().map(_.toXml) + + r + } + + def performFollow(userName: Can[String])(): LiftResponse = { + val r: Can[Boolean] = + for (user <- User.currentUser; + userName <- userName; + other <- User.findFromWeb(userName) + ) yield user.follow(other) + + r + } + + def performUnfollow(userName: Can[String])(): LiftResponse = { + val r: Can[Boolean] = + for (user <- User.currentUser; + userName <- userName; + other <- User.findFromWeb(userName) + ) yield user.unfollow(other) + + r + } + + def login(): LiftResponse = { + val res: Can[Boolean] = if (User.loggedIn_?) Empty else + for (token <- S.param("token") ?~ "No 'token' param"; + auth <- AuthToken.find(By(AuthToken.uniqueId, token)) + ?~ "Token not found"; + user <- auth.user.obj; + session <- S.session + ) yield { + User.logUserIn(user) + val myActor = buildActor(user.id) + restActor(Full(myActor)) + true + } + + res + } + + def logout(): LiftResponse = { + User.logUserOut() + true + } + + def waitForMsgs(): LiftResponse = { + val seq: Long = CometActor.next + + def waitForAnswer: Can[List[(Message, MailboxReason)]] = + receiveWithin(6L * 60L * 1000L) { + case (s2, ret: List[(Message, MailboxReason)]) if s2 == seq => + Full(ret) + case (s2, _, _) if s2 != seq => waitForAnswer + case _ => Empty + } + + var r: Can[NodeSeq] = + for (act <- restActor.is ?~ "No REST actor"; + val ignore = act ! ListenFor(self, 5 minutes, seq); + answer <- waitForAnswer ?~ "Didn't get an answer") + yield answer.flatMap{ case (msg, reason) => msg.toXml % reason.attr} + + r + } + + def sendMsgWithToken(req: Req): Can[LiftResponse] = { + for (token <- req.param("token"); + auth <- AuthToken.find(By(AuthToken.uniqueId, token)); + userId <- auth.user.can; + ret <- sendMsg(Full(userId), req)) yield ret + } + + def sendMsg(theUser: Can[Long], params: HasParams): LiftResponse = { + val r: Can[Boolean] = + for (user <- theUser ?~ "User not found"; + msg <- params.param("message") ?~ "Message not included") + yield { + val from: String = params.param("via") openOr "api" + + val xml: Can[Elem] = params.param("metadata").flatMap(md => + tryo(XML.loadString(md))) + + Distributor ! + Distributor.UserCreatedMessage(user, msg, + Tag.split(params.param("tags") + openOr ""), + millis, + xml, + from, + params.param("replyto").map(toLong)) + true + } + r + } + + def getMsgs(): LiftResponse = { + val t: Can[NodeSeq] = + for (tagName <- S.param("tag"); + tag <- Tag.find(By(Tag.name, tagName))) + yield tag.findMessages.map(_.toXml) + + val r: Can[NodeSeq] = + t or (for (user <- calcUser ?~ "User not found"; + val lst = Mailbox.mostRecentMessagesFor(user.id, 40)) + yield lst.flatMap{ case (msg, why) => msg.toXml % why.attr}) + + r + } + + def getTagCloud(params: HasParams): LiftResponse = { + // By default, get the normalised word frequencies from the messages + val numTags = (params.param("numTags") openOr "20").toInt + val numMsgs = 40 //(params.param("numMsgs") openOr "40").toInt + + val r: Can[NodeSeq] = + for (user <- calcUser ?~ "User not found"; + val lst = Mailbox.mostRecentMessagesFor(user.id, numMsgs).map(_._1)) + yield + + { + for (t <- Tag.centreWeightedTopNTagFreqs(lst, numTags)) yield + } + { + for (t <- Message.centreWeightedTopNWordFreqs(lst, numTags)) yield + } + + + + r + } + + def createTag(in: NodeSeq) = {in} + + + private def buildActor(userId: Long): RestActor = { + val ret = new RestActor + ret.start + ret ! StartUp(userId) + ret + } + + object restActor extends SessionVar[Can[RestActor]](Empty) { + override def cleanupFunc: Can[() => Unit] = Full(() => this.is.map(_ ! ByeBye)) + } + + + class RestActor extends Actor { + private var userId: Long = _ + private var msgs: List[(Message, MailboxReason)] = Nil + private var listener: Can[(Actor, Long)] = Empty + + def act = loop { + react { + case StartUp(userId) => + link(ActorWatcher) + this.userId = userId + Distributor ! Distributor.Listen(userId, this) + + case ByeBye => + unlink(ActorWatcher) + Distributor ! Distributor.Unlisten(userId, this) + self.exit() + + case UserActor.MessageReceived(msg, reason) => + msgs = (msg, reason) :: msgs + listener.foreach { + who => + who._1 ! (who._2, msgs) + listener = Empty + msgs = Nil + } + + case ReleaseListener => + listener.foreach(l => l._1 ! (l._2, Nil)) + listener = Empty + + case ListenFor(who, len, seq) => + msgs match { + case Nil => + listener.foreach(l => l._1 ! (l._2, Nil)) + listener = Full((who, seq)) + ActorPing.schedule(this, ReleaseListener, len) + + case xs => + who ! (seq, xs) + msgs = Nil + listener = Empty + } + } + } + } + + private case class StartUp(userId: Long) + private case object ByeBye + private case class ListenFor(who: Actor, howLong: TimeSpan, seq: Long) + private case object ReleaseListener +} Added: incubator/esme/trunk/server/src/main/scala/us/esme/comet/.keep URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/comet/.keep?rev=726084&view=auto ============================================================================== (empty) Added: incubator/esme/trunk/server/src/main/scala/us/esme/comet/PublicTimeline.scala URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/comet/PublicTimeline.scala?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/scala/us/esme/comet/PublicTimeline.scala (added) +++ incubator/esme/trunk/server/src/main/scala/us/esme/comet/PublicTimeline.scala Fri Dec 12 10:32:17 2008 @@ -0,0 +1,87 @@ + +/* + * 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.comet + +import net.liftweb.http._ +import net.liftweb.mapper._ +import net.liftweb.util._ +import net.liftweb.util.Helpers._ +import scala.xml._ +import js._ +import JsCmds._ +import JE._ + +import us.esme._ +import actor._ +import model._ +import lib._ + +import java.text._ + +class PublicTimeline extends CometActor with MsgFormat { + + def defaultPrefix = "timeline" + + private var messages: List[Long] = Nil + private var lastRender = millis + private var scheduled = false + + override def localSetup() { + super.localSetup() + Distributor ! Distributor.PublicTimelineListeners(this) + messages = Message.findAll(OrderBy(Message.id, Descending), + MaxRows(40)).map(_.id.is) + } + + override def localShutdown() { + super.localShutdown() + Distributor ! Distributor.PublicTimelineUnlisteners(this) + } + + def render = { + lastRender = millis + scheduled = false + val msgMap = Message.findMessages(messages) + val toDisplay = messages.flatMap(msgMap.get) + + + { + toDisplay.map(m => formatMsg(m, true, true)) + } + + } + + override implicit def xmlToXmlOrJsCmd(in: NodeSeq): RenderOut = new RenderOut(Full(in), fixedRender, Empty, Empty, false) + + override def lowPriority = { + case ForceRender => + reRender(false) + + case Distributor.NewMessage(msg) => + messages = (msg.id.is :: messages).take(40) + + if ((millis - lastRender) < 30000L) { + if (!scheduled) { + scheduled = true + ActorPing.schedule(this, ForceRender, 30000L) + } + } + else reRender(false) + } +} + +case object ForceRender Added: incubator/esme/trunk/server/src/main/scala/us/esme/comet/TagCloud.scala URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/comet/TagCloud.scala?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/scala/us/esme/comet/TagCloud.scala (added) +++ incubator/esme/trunk/server/src/main/scala/us/esme/comet/TagCloud.scala Fri Dec 12 10:32:17 2008 @@ -0,0 +1,95 @@ + +/* + * 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.comet + +import net.liftweb.http._ +import net.liftweb.util._ +import net.liftweb.util.Helpers._ +import scala.xml._ +import js._ +import JsCmds._ +import JE._ + +import us.esme._ +import actor._ +import model._ +import lib._ + +import java.text._ + +class TagCloud extends CometActor with MsgFormat { + + def defaultPrefix = "tagcloud" + + private var messages: List[Long] = Nil + + override def localSetup() { + super.localSetup() + for (user <- User.currentUser) { + Distributor ! Distributor.Listen(user.id, this) + Distributor !? (2000, Distributor.LatestMessages(user.id, 40)) match { + case Some(msg: List[Long]) => messages = msg + case x => + } + } + } + + override def localShutdown() { + super.localShutdown() + for (user <- User.currentUser) { + Distributor ! Distributor.Unlisten(user.id, this) + } + } + + private def lookupMessages(): List[Message] = { + val mm = Message.findMessages(messages) + messages.flatMap(mm.get) + } + + def render = { + val messages = lookupMessages() + //Sort the tags & words to put the most prominent in the middle +
+

Tags

+

+ { + for ((name, weight) <- Tag.centreWeightedTopNTagFreqs(messages, 20)) + yield { + name} + } +

+

+

Words

+

+ { + for ((name, weight) <- Message.centreWeightedTopNWordFreqs(messages, 20)) + yield { + name} + } +

+
+ } + + + override def lowPriority = { + case UserActor.MessageReceived(msg, _) => + messages = (msg.id.is :: messages).take(40) + reRender(false) + } +} Added: incubator/esme/trunk/server/src/main/scala/us/esme/comet/Timeline.scala URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/comet/Timeline.scala?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/scala/us/esme/comet/Timeline.scala (added) +++ incubator/esme/trunk/server/src/main/scala/us/esme/comet/Timeline.scala Fri Dec 12 10:32:17 2008 @@ -0,0 +1,79 @@ + +/* + * 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.comet + +import net.liftweb.http._ +import net.liftweb.util._ +import net.liftweb.util.Helpers._ +import scala.xml._ +import js._ +import JsCmds._ +import JE._ + +import us.esme._ +import actor._ +import model._ +import lib._ + +import java.text._ + +class Timeline extends CometActor with MsgFormat { + + def defaultPrefix = "timeline" + + private var messages: List[Long] = Nil + + override def localSetup() { + super.localSetup() + for (user <- User.currentUser) { + Distributor ! Distributor.Listen(user.id, this) + Distributor !? (200, Distributor.LatestMessages(user.id, 40)) match { + case Some(msg: List[Long]) => messages = msg + case x => + } + } + } + + override def localShutdown() { + super.localShutdown() + for (user <- User.currentUser) { + Distributor ! Distributor.Unlisten(user.id, this) + } + } + + def render = { + val msgMap = Message.findMessages(messages) + val toDisplay = messages.flatMap(msgMap.get) + + + { + toDisplay.map(m => {formatMsg(m, true, true)}) + } + + } + + override implicit def xmlToXmlOrJsCmd(in: NodeSeq): RenderOut = new RenderOut(Full(in), fixedRender, Empty, Empty, false) + + override def lowPriority = { + case UserActor.MessageReceived(msg, _) => + messages = (msg.id.is :: messages).take(40) + reRender(false) + + case Distributor.UserUpdated(_) => + reRender(false) + } +} Added: incubator/esme/trunk/server/src/main/scala/us/esme/external/TwitterFeed.scala URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/external/TwitterFeed.scala?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/scala/us/esme/external/TwitterFeed.scala (added) +++ incubator/esme/trunk/server/src/main/scala/us/esme/external/TwitterFeed.scala Fri Dec 12 10:32:17 2008 @@ -0,0 +1,56 @@ +package us.esme.external + +/* + * 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 us.esme.actor.UniqueMessageSource +import us.esme.actor.Distributor.UserCreatedMessage +import us.esme.model.User + +import net.liftweb._ +import util._ + +object TwitterFeed { + val UserTimelineUrl = "http://twitter.com/statuses/user_timeline/" + val Protocol = "xml" + val Params = "" + val tf = new java.text.SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", java.util.Locale.US) + +} + +class TwitterFeed(val user: User, val twitterUser: String) extends UniqueMessageSource { + import java.net.{URLConnection, URL} + import scala.xml._ + import TwitterFeed._ + + override def apply() = { + val url = new URL(UserTimelineUrl + twitterUser + "." + Protocol + Params) + val conn = url.openConnection() + + + (for (node <- (XML.load(conn.getInputStream) \ "status")) yield UserCreatedMessage( + user.id, + node \ "text" text, + List("twitter"), + tf.parse(node \ "created_at" text).getTime, + Empty, + "twiiter", + Empty + )).toList + } + +} + Added: incubator/esme/trunk/server/src/main/scala/us/esme/lib/MsgFormat.scala URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/us/esme/lib/MsgFormat.scala?rev=726084&view=auto ============================================================================== --- incubator/esme/trunk/server/src/main/scala/us/esme/lib/MsgFormat.scala (added) +++ incubator/esme/trunk/server/src/main/scala/us/esme/lib/MsgFormat.scala Fri Dec 12 10:32:17 2008 @@ -0,0 +1,111 @@ + +/* + * 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 us.esme._ +import model._ + +import scala.xml.{NodeSeq, Node, Text} + +import net.liftweb._ +import util._ +import http._ +import Helpers._ + +import java.text.SimpleDateFormat + +object IsIE7 extends SessionVar[Boolean]({ + // "Firefox/3" + // "MSIE 7" + def findUserAgent(in: List[(String, String)]): Can[String] = + in.filter(_._1.equalsIgnoreCase("User-Agent")).map(_._2).firstOption + + val r: Can[Boolean] = + for (session <- S.session; + agent <- findUserAgent(session.initialHeaders)) yield + agent.indexOf("MSIE 7") >= 0 + + r openOr false +}) + +trait MsgFormat { + + def formatMsg(in: Message, showReply: Boolean, showConv: Boolean): NodeSeq = + formatMsg(in, showReply, showConv, Empty) + + def formatMsg(in: Message, showReply: Boolean, showConv: Boolean, + inside: Can[() => NodeSeq]): NodeSeq = +
+ + + + + + +
{ + val r: Can[NodeSeq] = + for (user <- in.author.obj; + image <- user.image if !IsIE7.is) yield {user.niceName}/ + + r openOr   + } +
+
+

{in.digestedXHTML}

+
+

{in.author.obj.map(u => {u.niceName}).openOr("")}: + { dateFormat(in.when)} { + if (showReply) reply + else Text("") + + } + { + in.conversation.can.toList.map(cid => conversation) + } +

+
+
+ { + in.tags.map(tag =>

{tag}

) + } +
+ { + inside.map(_()).openOr(Text("")) + } +
+ + private def dateFormat(in: Long): String = { + val delta = ((millis - in) / 1000L).toInt + + delta match { + case 1 => "1 Second ago" + case n if n < 60 => ""+n+" Seconds ago" + case n if n >= 60 && n < 120 => "1 Minute ago" + case n if n < 3600 => ""+(n / 60)+" Minutes ago" + case n => + (n / 3600) match { + case 1 => "1 Hour ago" + case n if n < 24 => ""+n+" Hours ago" + case _ => val df = new SimpleDateFormat("MM/dd HH:mm zzz") + dateFormatter.format(new java.util.Date(in)) + } + } + } +}