Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #1251 from lift/diego_issue_1251

Integrate LiftNamedComet into Lift
  • Loading branch information...
commit f023e886118b51f7d0d4b73ab48f09e07afd5f4d 2 parents c6224ce + d59a033
@dpp dpp authored
View
57 web/webkit/src/main/scala/net/liftweb/http/NamedCometActorSnippet.scala
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2007-2012 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 net.liftweb
+package http
+
+import common.Full
+import xml.NodeSeq
+
+/**
+ * This trait adds a named comet actor on the page. *
+ */
+trait NamedCometActorSnippet {
+
+ /**
+ * This is your Comet Class
+ */
+ def cometClass: String
+
+ /**
+ * This is how you are naming your comet actors.
+ * It can be as simple as
+ *
+ * <pre name="code" class="scala"><code>
+ * def name= S.param(p).openOr("A")
+ * </code></pre>
+ *
+ * This causes every comet actor for class @cometClass with the same @name
+ * to include the same Comet Actor
+ *
+ */
+ def name: String
+
+ /**
+ * The render method that inserts the <lift:comet> tag
+ * to add the comet actor to the page
+ */
+ final def render(xhtml: NodeSeq): NodeSeq = {
+ for (sess <- S.session) sess.sendCometActorMessage(
+ cometClass, Full(name), CometName(name)
+ )
+ <lift:comet type={cometClass} name={name}>{xhtml}</lift:comet>
+ }
+}
View
50 web/webkit/src/main/scala/net/liftweb/http/NamedCometActorTrait.scala
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2007-2012 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 net.liftweb
+package http
+
+import util.Helpers._
+import common.{Loggable, Full}
+
+
+trait NamedCometActorTrait extends CometActor with Loggable {
+
+ /**
+ * First thing we do is registering this comet actor
+ * for the "name" key
+ */
+ override def localSetup = {
+ NamedCometListener.getOrAddDispatchersFor(name).foreach(
+ dispatcher=> dispatcher ! registerCometActor(this, name)
+ )
+ super.localSetup()
+ }
+
+ /**
+ * We remove the CometActor from the map of registered actors
+ */
+ override def localShutdown = {
+ NamedCometListener.getOrAddDispatchersFor(name).foreach(
+ dispatcher=> dispatcher ! unregisterCometActor(this)
+ )
+ super.localShutdown()
+ }
+
+ // time out the comet actor if it hasn't been on a page for 2 minutes
+ override def lifespan = Full(120 seconds)
+
+}
View
95 web/webkit/src/main/scala/net/liftweb/http/NamedCometDispatcher.scala
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2007-2012 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 net.liftweb
+package http
+
+import common.{Box, Full, Loggable}
+import actor.LiftActor
+
+/**
+ * This class keeps a list of comet actors that need to update the UI
+ */
+class NamedCometDispatcher(name: Box[String]) extends LiftActor with Loggable {
+
+ logger.debug("DispatcherActor got name: %s".format(name))
+
+ private var cometActorsToUpdate: Vector[CometActor]= Vector()
+
+ override def messageHandler = {
+ /**
+ * if we do not have this actor in the list, add it (register it)
+ */
+ case registerCometActor(actor, Full(name)) => {
+ if(cometActorsToUpdate.contains(actor) == false){
+ logger.debug("We are adding actor: %s to the list".format(actor))
+ cometActorsToUpdate= cometActorsToUpdate :+ actor
+ } else {
+ logger.debug("The list so far is %s".format(cometActorsToUpdate))
+ }
+ }
+ case unregisterCometActor(actor) => {
+ logger.debug("before %s".format(cometActorsToUpdate))
+ cometActorsToUpdate= cometActorsToUpdate.filterNot(_ == actor)
+ logger.debug("after %s".format(cometActorsToUpdate))
+ }
+
+ //Catch the dummy message we send on comet creation
+ case CometName(name) =>
+
+ /**
+ * Go through the list of actors and send them a message
+ */
+ case msg => {
+ cometActorsToUpdate.par.foreach{ x => {
+ x ! msg
+ logger.debug("We will update this comet actor: %s showing name: %s".format(x, name))
+ }
+ }
+ }
+ }
+
+ /**
+ * Vector.par was introduced in 2.9.x , so to be able to use it in Lift builds for scala 2.8.x
+ * I am using this implicit conversion to avoid a compiler error.
+ * I fake the par method to return a simple Vector
+ *
+ */
+ implicit def fakeParFor2_8_x(v: Vector[CometActor]): VectorPar2_8_x = {
+ new VectorPar2_8_x(v)
+ }
+}
+
+/**
+ * Vector.par was introduced in 2.9.x , so to be able to use it in Lift builds for scala 2.8.x
+ * I am using this implicit conversion to avoid a compiler error.
+ * I fake the par method to return a simple Vector
+ *
+ * TODO: Remove after we no longer build for 2.8.x
+ */
+class VectorPar2_8_x(v: Vector[CometActor]) {
+ def par = v
+}
+
+
+/**
+ * These are the message we pass around to
+ * register each named comet actor with a dispatcher that
+ * only updates the specific version it monitors
+ */
+case class registerCometActor(actor: CometActor, name: Box[String])
+case class unregisterCometActor(actor: CometActor)
+case class CometName(name: String)
View
94 web/webkit/src/main/scala/net/liftweb/http/NamedCometListener.scala
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2007-2012 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 net.liftweb
+package http
+
+import util.Schedule
+import util.Helpers._
+import actor.{LAFuture, LiftActor}
+import common.{Empty, Full, Box, Loggable}
+
+/**
+ * Maintain a Map[Value the actor monitors -> Ref to the Actor Dispatcher]
+ *
+ * For a url like: http://hostnbame/index/?p=icecream
+ * If you name your actor based on the value of p
+ * For each flavor that users have on their urls,
+ * the map would be like:
+ * chocolate -> code.comet.CometClassNames@ea5e9e7 ,
+ * vanilla -> code.comet.CometClassNames@wv9i7o3, etc
+ *
+ * If we have the actor already on the Map, just return it,
+ * because it has to update the UI.
+ * If wee do not have this actor on our Map. create a new
+ * Dispatcher that will monitor this value, add it to our Map
+ * and return the Ref to this new dispatcher so it updates the UI
+ *
+ *
+ */
+object NamedCometListener extends Loggable {
+
+ /**
+ * The map of key -> Dispatchers
+ */
+ private var disptchers: Map[String, LiftActor] = Map()
+
+ /**
+ * Either returns or creates a dispatcher for the @str key
+ */
+ def getOrAddDispatchersFor(str: Box[String]): LAFuture[LiftActor] = synchronized {
+ val name= str.getOrElse("")
+ val liftActor: LAFuture[LiftActor] = new LAFuture()
+ Schedule{() => {
+ val ret= disptchers.get(name ) match {
+ case Some(actor) => actor
+ case None => {
+ val ret = new NamedCometDispatcher(str)
+ disptchers += name -> ret
+ logger.debug("Our map of NamedCometDispatchers is: %s".format(disptchers));
+ ret
+ }
+ }
+ liftActor.satisfy(ret)
+ }
+ }
+ liftActor
+ }
+
+ /**
+ * Returns a Future containing a Full dispatcher or None for the @str key
+ *
+ * A common use case for this method is if you are sending updates to comet actors from a rest endpoint,
+ * you do not want to create dispatchers if no comet is presently monitoring the @str key
+ *
+ */
+ def getDispatchersFor(str: Box[String]): LAFuture[Box[LiftActor]] = synchronized {
+ val name= str.getOrElse("")
+ val liftActor: LAFuture[Box[LiftActor]] = new LAFuture()
+ Schedule{() => {
+ val ret= disptchers.get(name ) match {
+ case Some(actor) => Full(actor)
+ case None => Empty
+ }
+ liftActor.satisfy(ret)
+ }
+ }
+ liftActor
+ }
+
+
+}
View
63 web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010-2012 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 net.liftweb
+package http
+
+import js.JsCmds
+import xml._
+import org.specs.Specification
+
+import common._
+
+/**
+ * System under specification for NamedComet* files.
+ */
+object NamedCometPerTabSpec extends Specification("NamedCometPerTabSpec Specification") {
+
+ class CometA extends NamedCometActorTrait{
+ override def lowPriority = {
+ case msg => JsCmds.Noop
+ }
+ def render = {
+ "nada" #> Text("nada")
+ }
+ }
+
+ "A NamedCometDispatcher" should {
+ doBefore {
+ val cometA= new CometA{override def name= Full("1")}
+ cometA.localSetup
+ }
+ "be created for a comet" in {
+ NamedCometListener.getDispatchersFor(Full("1")).foreach(
+ actor => actor.open_!.toString must startWith("net.liftweb.http.NamedCometDispatcher")
+ )
+ }
+ "be created even if no comet is present when calling getOrAddDispatchersFor" in {
+ NamedCometListener.getOrAddDispatchersFor(Full("3")).foreach(
+ actor => actor.toString must startWith("net.liftweb.http.NamedCometDispatcher")
+ )
+ }
+ "not be created for a non existing key" in {
+ NamedCometListener.getDispatchersFor(Full("2")).foreach(
+ actor => actor must_== Empty
+ )
+ }
+ }
+
+
+}
Please sign in to comment.
Something went wrong with that request. Please try again.