Skip to content

Commit

Permalink
Added Dave's parallel snipets. moved Lift merge to LiftMerge trait
Browse files Browse the repository at this point in the history
  • Loading branch information
Marius Danciu authored and dpp committed Oct 9, 2009
1 parent 7a4708d commit b798ce4
Show file tree
Hide file tree
Showing 7 changed files with 516 additions and 199 deletions.
233 changes: 233 additions & 0 deletions lift/src/main/scala/net/liftweb/http/LiftMerge.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package net.liftweb.http

import _root_.scala.collection.mutable.{HashMap, ArrayBuffer, ListBuffer}
import _root_.scala.xml.{Node, Text, NodeSeq, Elem, MetaData, Null, UnprefixedAttribute, PrefixedAttribute, Comment, Group}
import _root_.net.liftweb.util._
import _root_.net.liftweb.http.js._
import Helpers._


private[http] trait LiftMerge {

self : LiftSession =>

/**
* Manages the merge phase of the rendering pipeline
*/
private[http] def merge(xhtml: NodeSeq, req: Req): Node = {
val snippetHashs: HashMap[String, Box[NodeSeq]] = this.deferredSnippets.is
val waitUntil = millis + LiftRules.lazySnippetTimeout.vend.millis
val stripComments: Boolean = LiftRules.stripComments.vend()

def waitUntilSnippetsDone() {
val myMillis = millis
snippetHashs.synchronized {
if (myMillis >= waitUntil || snippetHashs.isEmpty || !snippetHashs.values.contains(Empty)) ()
else {
snippetHashs.wait(waitUntil - myMillis)
waitUntilSnippetsDone()
}
}
}

waitUntilSnippetsDone()

val processedSnippets: Map[String, NodeSeq] = Map( snippetHashs.toList.flatMap {
case (name, Full(value)) => List((name, value))
case (name, f: Failure) => List((name, LiftRules.deferredSnippetFailure.vend(f)))
case (name, Empty) => List((name, LiftRules.deferredSnippetTimeout.vend()))
case _ => Nil
} :_*)

val hasHtmlHeadAndBody: Boolean = xhtml.find {
case e: Elem if e.label == "html" =>
e.child.find {
case e: Elem if e.label == "head" => true
case _ => false
}.isDefined &&
e.child.find {
case e: Elem if e.label == "body" => true
case _ => false
}.isDefined
case _ => false
}.isDefined


var htmlTag = <html xmlns="http://www.w3.org/1999/xhtml" xmlns:lift='http://liftweb.net'/>
var headTag = <head/>
var bodyTag = <body/>
val headChildren = new ListBuffer[Node]
val bodyChildren = new ListBuffer[Node]
val addlHead = new ListBuffer[Node]
val addlTail = new ListBuffer[Node]
val cometTimes = new ListBuffer[CometVersionPair]
val rewrite = URLRewriter.rewriteFunc
val fixHref = Req.fixHref

val contextPath: String = this.contextPath

def fixAttrs(original: MetaData, toFix : String, attrs : MetaData, fixURL: Boolean) : MetaData = attrs match {
case Null => Null
case p: PrefixedAttribute if p.key == "when" && p.pre == "lift" =>
val when = p.value.text
original.find(a => !a.isPrefixed && a.key == "id").map {
id =>
cometTimes += CVP(id.value.text, when.toLong)
}
fixAttrs(original, toFix, p.next, fixURL)
case u: UnprefixedAttribute if u.key == toFix =>
new UnprefixedAttribute(toFix, fixHref(contextPath, attrs.value, fixURL, rewrite),fixAttrs(original, toFix, attrs.next, fixURL))
case _ => attrs.copy(fixAttrs(original, toFix, attrs.next, fixURL))

}

def _fixHtml(in: NodeSeq, _inHtml: Boolean, _inHead: Boolean, _justHead: Boolean, _inBody: Boolean, _justBody: Boolean, _bodyHead: Boolean, _bodyTail: Boolean, doMergy: Boolean): NodeSeq = {
in.flatMap{
v =>
var inHtml = _inHtml
var inHead = _inHead
var justHead = false
var justBody = false
var inBody = _inBody
var bodyHead = false
var bodyTail = false

v match {
case e: Elem if e.label == "html" && !inHtml => htmlTag = e; inHtml = true && doMergy
case e: Elem if e.label == "head" && inHtml && !inBody => headTag = e; inHead = true && doMergy; justHead = true && doMergy
case e: Elem if e.label == "head" && inHtml && inBody => bodyHead = true && doMergy
case e: Elem if e.label == "tail" && inHtml && inBody => bodyTail = true && doMergy
case e: Elem if e.label == "body" && inHtml => bodyTag = e; inBody = true && doMergy; justBody = true && doMergy

case _ =>
}

val ret: NodeSeq = v match {
case Group(nodes) => Group(_fixHtml( nodes, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy))

// if it's a deferred node, grab it from the deferred list
case e: Elem if e.label == "node" && e.prefix == "lift_deferred" =>
for {
attr <- e.attributes("id").firstOption.map(_.text).toList
nodes <- processedSnippets.get(attr).toList
node <- _fixHtml( nodes, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy)
} yield node

case e: Elem if e.label == "form" => Elem(v.prefix, v.label, fixAttrs(v.attributes, "action", v.attributes, true), v.scope, _fixHtml(v.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) : _* )
case e: Elem if e.label == "script" => Elem(v.prefix, v.label, fixAttrs(v.attributes, "src", v.attributes, false), v.scope, _fixHtml(v.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) : _* )
case e: Elem if e.label == "a" => Elem(v.prefix, v.label, fixAttrs(v.attributes, "href", v.attributes, true), v.scope, _fixHtml( v.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) : _* )
case e: Elem if e.label == "link" => Elem(v.prefix, v.label, fixAttrs(v.attributes, "href", v.attributes, false), v.scope, _fixHtml( v.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) : _* )
case e: Elem => Elem(v.prefix, v.label, fixAttrs(v.attributes, "src", v.attributes, true), v.scope, _fixHtml( v.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) : _*)
case c: Comment if stripComments => NodeSeq.Empty
case _ => v
}
if (_justHead) headChildren ++= ret
else if (_justBody && !bodyHead && !bodyTail) bodyChildren ++= ret
else if (_bodyHead) addlHead ++= ret
else if (_bodyTail) addlTail ++= ret

if (bodyHead || bodyTail) Text("")
else ret
}
}

if (!hasHtmlHeadAndBody) {
val fixedHtml = _fixHtml(xhtml, false, false, false, false, false, false, false, false)

fixedHtml.find {
case e: Elem => true
case _ => false
} getOrElse Text("")
} else {
_fixHtml(xhtml, false, false, false, false, false, false,false, true)

val htmlKids = new ListBuffer[Node]

val nl = Text("\n")

for {
node <- HeadHelper.removeHtmlDuplicates(addlHead.toList)
} {
headChildren += node
headChildren += nl
}

// Appends ajax stript to body
if (LiftRules.autoIncludeAjax(this)) {
bodyChildren +=
<script src={S.encodeURL(contextPath+"/"+
LiftRules.ajaxPath +
"/" + LiftRules.ajaxScriptName())}
type="text/javascript"/>
bodyChildren += nl
}

val cometList = cometTimes.toList

// Appends comet stript reference to head
if (!cometList.isEmpty && LiftRules.autoIncludeComet(this)) {
bodyChildren +=
<script src={S.encodeURL(contextPath+"/"+
LiftRules.cometPath +
"/" + urlEncode(this.uniqueId) +
"/" + LiftRules.cometScriptName())}
type="text/javascript"/>
bodyChildren += nl
}

for {
node <- HeadHelper.removeHtmlDuplicates(addlTail.toList)
} bodyChildren += node

bodyChildren += nl

if (!cometList.isEmpty && LiftRules.autoIncludeComet(this)) {
bodyChildren += JsCmds.Script(LiftRules.renderCometPageContents(this, cometList))
bodyChildren += nl
}

if (LiftRules.enableLiftGC) {
import js._
import JsCmds._
import JE._

bodyChildren += JsCmds.Script(OnLoad(JsRaw("liftAjax.lift_successRegisterGC()")) &
JsCrVar("lift_page", RenderVersion.get))
}

htmlKids += nl
htmlKids += Elem(headTag.prefix, headTag.label, headTag.attributes, headTag.scope, headChildren.toList :_*)
htmlKids += nl
htmlKids += Elem(bodyTag.prefix, bodyTag.label, bodyTag.attributes, bodyTag.scope, bodyChildren.toList :_*)
htmlKids += nl

val tmpRet = Elem(htmlTag.prefix, htmlTag.label, htmlTag.attributes, htmlTag.scope, htmlKids.toList :_*)

val ret: Node = if (Props.devMode) {
LiftRules.xhtmlValidator.toList.flatMap(_(tmpRet)) match {
case Nil => tmpRet
case xs =>
import _root_.scala.xml.transform._

val errors: NodeSeq = xs.map(e =>
<div style="border: red solid 2px">
XHTML Validation error: {e.msg} at line {e.line + 1} and column {e.col}
</div>)

val rule = new RewriteRule {
override def transform(n: Node) = n match {
case e: Elem if e.label == "body" =>
Elem(e.prefix, e.label, e.attributes, e.scope,e.child ++ errors :_*)

case x => super.transform(x)
}
}
(new RuleTransformer(rule)).transform(tmpRet)(0)
}

} else tmpRet

ret
}
}
}
72 changes: 70 additions & 2 deletions lift/src/main/scala/net/liftweb/http/LiftRules.scala
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,74 @@ object LiftRules extends Factory {

def siteMap: Box[SiteMap] = _sitemap

/**
* How long should we wait for all the lazy snippets to render
*/
val lazySnippetTimeout: FactoryMaker[TimeSpan] = new FactoryMaker(() => 30 seconds) {}

/**
* Does the current context support parallel snippet execution
*/
val allowParallelSnippets: FactoryMaker[Boolean] = new FactoryMaker(() => false) {}

/**
* If a deferred snippet has a failure during render,
* what should we display?
*/
val deferredSnippetFailure: FactoryMaker[Failure => NodeSeq] =
new FactoryMaker(() => {failure: Failure => {
if (Props.devMode)
<div style="border: red solid 2px">
A lift:parallel snippet failed to render. Message: {failure.msg}
{
failure.exception match {
case Full(e) =>
<pre>{
e.getStackTrace.map(_.toString).mkString("\n")
}</pre>
case _ => NodeSeq.Empty
}
}

<i>note: this error is displayed in the browser because
your application is running in "development" mode. If you
set the system property run.mode=production, this error will not
be displayed, but there will be errors in the output logs.
</i>
</div>
else NodeSeq.Empty
}}) {}

/**
* If a deferred snippet has a failure during render,
* what should we display?
*/
val deferredSnippetTimeout: FactoryMaker[() => NodeSeq] =
new FactoryMaker(() => {() => {
if (Props.devMode)
<div style="border: red solid 2px">
A deferred snippet timed out during render.

<i>note: this error is displayed in the browser because
your application is running in "development" mode. If you
set the system property run.mode=production, this error will not
be displayed, but there will be errors in the output logs.
</i>
</div>
else NodeSeq.Empty
}}) {}

/**
* Should comments be stripped from the served XHTML
*/
val stripComments: FactoryMaker[() => Boolean] =
new FactoryMaker(() => {() => {
if (Props.devMode)
false
else true
}}) {}


private[http] var ending = false

private[http] var doneBoot = false;
Expand Down Expand Up @@ -787,14 +855,14 @@ object LiftRules extends Factory {
* be copied from the snippet invocation tag to the form tag. The
* default list is "class", "id", "target", "style", "onsubmit"
*/
val formAttrs: FactoryMaker[List[String]] = new FactoryMaker(() => List("class", "id", "target", "style", "onsubmit")) {}
val formAttrs: FactoryMaker[List[String]] = new FactoryMaker(() => List("class", "id", "target", "style", "onsubmit")) {}

/**
* By default, Http response headers are appended. However, there are
* some headers that should only appear once (for example "expires"). This
* Vendor vends the list of header responses that can only appear once.
*/
val overwrittenReponseHeaders: FactoryMaker[List[String]] = new FactoryMaker(() => List("expires")) {}
val overwrittenReponseHeaders: FactoryMaker[List[String]] = new FactoryMaker(() => List("expires")) {}

/**
* A utility method to convert an exception to a string of stack traces
Expand Down
Loading

0 comments on commit b798ce4

Please sign in to comment.