Skip to content
This repository
Browse code

Merge pull request #1251 from lift/diego_issue_1251

Integrate LiftNamedComet into Lift
  • Loading branch information...
commit f023e886118b51f7d0d4b73ab48f09e07afd5f4d 2 parents c6224ce + d59a033
David Pollak authored May 14, 2012
57  web/webkit/src/main/scala/net/liftweb/http/NamedCometActorSnippet.scala
... ...
@@ -0,0 +1,57 @@
  1
+/*
  2
+ * Copyright 2007-2012 WorldWide Conferencing, LLC
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License");
  5
+ * you may not use this file except in compliance with the License.
  6
+ * You may obtain a copy of the License at
  7
+ *
  8
+ *     http://www.apache.org/licenses/LICENSE-2.0
  9
+ *
  10
+ * Unless required by applicable law or agreed to in writing, software
  11
+ * distributed under the License is distributed on an "AS IS" BASIS,
  12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+ * See the License for the specific language governing permissions and
  14
+ * limitations under the License.
  15
+ */
  16
+
  17
+package net.liftweb
  18
+package http
  19
+
  20
+import common.Full
  21
+import xml.NodeSeq
  22
+
  23
+/**
  24
+ * This trait adds a named comet actor on the page. *
  25
+ */
  26
+trait NamedCometActorSnippet {
  27
+
  28
+  /**
  29
+   * This is your Comet Class
  30
+   */
  31
+  def cometClass: String
  32
+
  33
+  /**
  34
+   * This is how you are naming your comet actors.
  35
+   * It can be as simple as
  36
+   *
  37
+   * <pre name="code" class="scala"><code>
  38
+   *   def name= S.param(p).openOr("A")
  39
+   * </code></pre>
  40
+   *
  41
+   * This causes every comet actor for class @cometClass with the same @name
  42
+   * to include the same Comet Actor
  43
+   *
  44
+   */
  45
+  def name: String
  46
+
  47
+  /**
  48
+   * The render method that inserts the <lift:comet> tag
  49
+   * to add the comet actor to the page
  50
+   */
  51
+  final def render(xhtml: NodeSeq): NodeSeq = {
  52
+    for (sess <- S.session) sess.sendCometActorMessage(
  53
+      cometClass, Full(name), CometName(name)
  54
+    )
  55
+    <lift:comet type={cometClass} name={name}>{xhtml}</lift:comet>
  56
+  }
  57
+}
50  web/webkit/src/main/scala/net/liftweb/http/NamedCometActorTrait.scala
... ...
@@ -0,0 +1,50 @@
  1
+/*
  2
+ * Copyright 2007-2012 WorldWide Conferencing, LLC
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License");
  5
+ * you may not use this file except in compliance with the License.
  6
+ * You may obtain a copy of the License at
  7
+ *
  8
+ *     http://www.apache.org/licenses/LICENSE-2.0
  9
+ *
  10
+ * Unless required by applicable law or agreed to in writing, software
  11
+ * distributed under the License is distributed on an "AS IS" BASIS,
  12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+ * See the License for the specific language governing permissions and
  14
+ * limitations under the License.
  15
+ */
  16
+
  17
+package net.liftweb
  18
+package http
  19
+
  20
+import util.Helpers._
  21
+import common.{Loggable, Full}
  22
+
  23
+
  24
+trait NamedCometActorTrait extends CometActor with Loggable {
  25
+
  26
+  /**
  27
+   * First thing we do is registering this comet actor
  28
+   * for the "name" key
  29
+   */
  30
+  override  def localSetup = {
  31
+    NamedCometListener.getOrAddDispatchersFor(name).foreach(
  32
+      dispatcher=> dispatcher ! registerCometActor(this, name)
  33
+    )
  34
+    super.localSetup()
  35
+  }
  36
+
  37
+  /**
  38
+   * We remove the CometActor from the map of registered actors
  39
+   */
  40
+  override  def localShutdown = {
  41
+    NamedCometListener.getOrAddDispatchersFor(name).foreach(
  42
+      dispatcher=> dispatcher !  unregisterCometActor(this)
  43
+    )
  44
+    super.localShutdown()
  45
+  }
  46
+
  47
+  // time out the comet actor if it hasn't been on a page for 2 minutes
  48
+  override def lifespan = Full(120 seconds)
  49
+
  50
+}
95  web/webkit/src/main/scala/net/liftweb/http/NamedCometDispatcher.scala
... ...
@@ -0,0 +1,95 @@
  1
+/*
  2
+ * Copyright 2007-2012 WorldWide Conferencing, LLC
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License");
  5
+ * you may not use this file except in compliance with the License.
  6
+ * You may obtain a copy of the License at
  7
+ *
  8
+ *     http://www.apache.org/licenses/LICENSE-2.0
  9
+ *
  10
+ * Unless required by applicable law or agreed to in writing, software
  11
+ * distributed under the License is distributed on an "AS IS" BASIS,
  12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+ * See the License for the specific language governing permissions and
  14
+ * limitations under the License.
  15
+ */
  16
+
  17
+package net.liftweb
  18
+package http
  19
+
  20
+import common.{Box, Full, Loggable}
  21
+import actor.LiftActor
  22
+
  23
+/**
  24
+ * This class keeps a list of comet actors that need to update the UI
  25
+ */
  26
+class NamedCometDispatcher(name: Box[String]) extends LiftActor with Loggable {
  27
+
  28
+  logger.debug("DispatcherActor got name: %s".format(name))
  29
+
  30
+  private var cometActorsToUpdate: Vector[CometActor]= Vector()
  31
+
  32
+  override def messageHandler  = {
  33
+    /**
  34
+     * if we do not have this actor in the list, add it (register it)
  35
+     */
  36
+    case registerCometActor(actor, Full(name)) => {
  37
+      if(cometActorsToUpdate.contains(actor) == false){
  38
+        logger.debug("We are adding actor: %s to the list".format(actor))
  39
+        cometActorsToUpdate= cometActorsToUpdate :+ actor
  40
+      } else {
  41
+        logger.debug("The list so far is %s".format(cometActorsToUpdate))
  42
+      }
  43
+    }
  44
+    case unregisterCometActor(actor) => {
  45
+      logger.debug("before %s".format(cometActorsToUpdate))
  46
+      cometActorsToUpdate= cometActorsToUpdate.filterNot(_ == actor)
  47
+      logger.debug("after %s".format(cometActorsToUpdate))
  48
+    }
  49
+
  50
+    //Catch the dummy message we send on comet creation
  51
+    case CometName(name) =>
  52
+
  53
+    /**
  54
+     * Go through the list of actors and send them a message
  55
+     */
  56
+    case msg => {
  57
+      cometActorsToUpdate.par.foreach{ x => {
  58
+        x ! msg
  59
+        logger.debug("We will update this comet actor: %s showing name: %s".format(x, name))
  60
+      }
  61
+      }
  62
+    }
  63
+  }
  64
+
  65
+  /**
  66
+   * Vector.par was introduced in 2.9.x , so to be able to use it in Lift builds for scala 2.8.x
  67
+   * I am using this implicit conversion to avoid a compiler error.
  68
+   * I fake the par method to return a simple Vector
  69
+   *
  70
+   */
  71
+  implicit def fakeParFor2_8_x(v: Vector[CometActor]): VectorPar2_8_x = {
  72
+    new VectorPar2_8_x(v)
  73
+  }
  74
+}
  75
+
  76
+/**
  77
+ * Vector.par was introduced in 2.9.x , so to be able to use it in Lift builds for scala 2.8.x
  78
+ * I am using this implicit conversion to avoid a compiler error.
  79
+ * I fake the par method to return a simple Vector
  80
+ *
  81
+ * TODO: Remove after we no longer build for 2.8.x
  82
+ */
  83
+class VectorPar2_8_x(v: Vector[CometActor]) {
  84
+  def par = v
  85
+}
  86
+
  87
+
  88
+/**
  89
+ * These are the message we pass around to
  90
+ * register each named comet actor with a dispatcher that
  91
+ * only updates the specific version it monitors
  92
+ */
  93
+case class registerCometActor(actor: CometActor, name: Box[String])
  94
+case class unregisterCometActor(actor: CometActor)
  95
+case class CometName(name: String)
94  web/webkit/src/main/scala/net/liftweb/http/NamedCometListener.scala
... ...
@@ -0,0 +1,94 @@
  1
+/*
  2
+ * Copyright 2007-2012 WorldWide Conferencing, LLC
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License");
  5
+ * you may not use this file except in compliance with the License.
  6
+ * You may obtain a copy of the License at
  7
+ *
  8
+ *     http://www.apache.org/licenses/LICENSE-2.0
  9
+ *
  10
+ * Unless required by applicable law or agreed to in writing, software
  11
+ * distributed under the License is distributed on an "AS IS" BASIS,
  12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+ * See the License for the specific language governing permissions and
  14
+ * limitations under the License.
  15
+ */
  16
+
  17
+package net.liftweb
  18
+package http
  19
+
  20
+import util.Schedule
  21
+import util.Helpers._
  22
+import actor.{LAFuture, LiftActor}
  23
+import common.{Empty, Full, Box, Loggable}
  24
+
  25
+/**
  26
+ * Maintain a Map[Value the actor monitors -> Ref to the Actor Dispatcher]
  27
+ *
  28
+ * For a url like: http://hostnbame/index/?p=icecream
  29
+ * If you name your actor based on the value of p
  30
+ * For each flavor that users have on their urls,
  31
+ * the map would be like:
  32
+ * chocolate -> code.comet.CometClassNames@ea5e9e7 ,
  33
+ * vanilla   -> code.comet.CometClassNames@wv9i7o3, etc
  34
+ *
  35
+ * If we have the actor already on the Map, just return it,
  36
+ * because it has to update the UI.
  37
+ * If wee do not have this actor on our Map. create a new
  38
+ * Dispatcher that will monitor this value, add it to our Map
  39
+ * and return the Ref to this new dispatcher so it updates the UI
  40
+ *
  41
+ *
  42
+ */
  43
+object NamedCometListener extends Loggable  {
  44
+
  45
+  /**
  46
+   * The map of key -> Dispatchers
  47
+   */
  48
+  private var disptchers: Map[String, LiftActor] = Map()
  49
+
  50
+  /**
  51
+   * Either returns  or creates a dispatcher for the @str key
  52
+   */
  53
+  def getOrAddDispatchersFor(str: Box[String]): LAFuture[LiftActor] = synchronized {
  54
+    val name= str.getOrElse("")
  55
+    val liftActor: LAFuture[LiftActor] = new LAFuture()
  56
+    Schedule{() => {
  57
+      val ret= disptchers.get(name ) match {
  58
+        case Some(actor) => actor
  59
+        case None        => {
  60
+          val ret = new NamedCometDispatcher(str)
  61
+          disptchers += name -> ret
  62
+          logger.debug("Our map of NamedCometDispatchers is: %s".format(disptchers));
  63
+          ret
  64
+        }
  65
+      }
  66
+      liftActor.satisfy(ret)
  67
+    }
  68
+    }
  69
+    liftActor
  70
+  }
  71
+
  72
+  /**
  73
+   * Returns a Future containing a Full dispatcher or None for the @str key
  74
+   *
  75
+   * A common use case for this method is if you are sending updates to comet actors from a rest endpoint,
  76
+   * you do not want to create dispatchers if no comet is presently monitoring the @str key
  77
+   *
  78
+   */
  79
+  def getDispatchersFor(str: Box[String]): LAFuture[Box[LiftActor]] = synchronized {
  80
+    val name= str.getOrElse("")
  81
+    val liftActor: LAFuture[Box[LiftActor]] = new LAFuture()
  82
+    Schedule{() => {
  83
+      val ret= disptchers.get(name ) match {
  84
+        case Some(actor) => Full(actor)
  85
+        case None        => Empty
  86
+      }
  87
+      liftActor.satisfy(ret)
  88
+    }
  89
+    }
  90
+    liftActor
  91
+  }
  92
+
  93
+
  94
+}
63  web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala
... ...
@@ -0,0 +1,63 @@
  1
+/*
  2
+ * Copyright 2010-2012 WorldWide Conferencing, LLC
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License");
  5
+ * you may not use this file except in compliance with the License.
  6
+ * You may obtain a copy of the License at
  7
+ *
  8
+ *     http://www.apache.org/licenses/LICENSE-2.0
  9
+ *
  10
+ * Unless required by applicable law or agreed to in writing, software
  11
+ * distributed under the License is distributed on an "AS IS" BASIS,
  12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+ * See the License for the specific language governing permissions and
  14
+ * limitations under the License.
  15
+ */
  16
+
  17
+package net.liftweb
  18
+package http
  19
+
  20
+import js.JsCmds
  21
+import xml._
  22
+import org.specs.Specification
  23
+
  24
+import common._
  25
+
  26
+/**
  27
+ * System under specification for NamedComet* files.
  28
+ */
  29
+object NamedCometPerTabSpec extends Specification("NamedCometPerTabSpec Specification") {
  30
+
  31
+  class CometA extends NamedCometActorTrait{
  32
+    override def lowPriority = {
  33
+      case msg => JsCmds.Noop
  34
+    }
  35
+    def render = {
  36
+      "nada" #> Text("nada")
  37
+    }
  38
+  }
  39
+
  40
+  "A NamedCometDispatcher" should {
  41
+    doBefore {
  42
+      val cometA= new CometA{override def name= Full("1")}
  43
+      cometA.localSetup
  44
+    }
  45
+    "be created for a comet" in {
  46
+      NamedCometListener.getDispatchersFor(Full("1")).foreach(
  47
+        actor => actor.open_!.toString must startWith("net.liftweb.http.NamedCometDispatcher")
  48
+      )
  49
+    }
  50
+    "be created even if no comet is present when calling getOrAddDispatchersFor" in {
  51
+      NamedCometListener.getOrAddDispatchersFor(Full("3")).foreach(
  52
+        actor => actor.toString must startWith("net.liftweb.http.NamedCometDispatcher")
  53
+      )
  54
+    }
  55
+    "not be created for a non existing key" in {
  56
+      NamedCometListener.getDispatchersFor(Full("2")).foreach(
  57
+        actor => actor must_== Empty
  58
+      )
  59
+    }
  60
+  }
  61
+
  62
+
  63
+}

0 notes on commit f023e88

Please sign in to comment.
Something went wrong with that request. Please try again.